uilint-eslint 0.2.75 → 0.2.77

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/create-rule.ts","../../src/rules/no-mixed-component-libraries/lib/export-resolver.ts","../../src/rules/no-mixed-component-libraries/lib/component-parser.ts","../../src/rules/no-mixed-component-libraries/lib/import-graph.ts","../../src/rules/no-mixed-component-libraries/index.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\n/**\n * Schema for prompting user to configure a rule option in the CLI\n */\nexport interface OptionFieldSchema {\n /** Field name in the options object */\n key: string;\n /** Display label for the prompt */\n label: string;\n /** Prompt type */\n type: \"text\" | \"number\" | \"boolean\" | \"select\" | \"multiselect\";\n /** Default value */\n defaultValue: unknown;\n /** Placeholder text (for text/number inputs) */\n placeholder?: string;\n /** Options for select/multiselect */\n options?: Array<{ value: string | number; label: string }>;\n /** Description/hint for the field */\n description?: string;\n}\n\n/**\n * Schema describing how to prompt for rule options during installation\n */\nexport interface RuleOptionSchema {\n /** Fields that can be configured for this rule */\n fields: OptionFieldSchema[];\n}\n\n/**\n * External requirement that a rule needs to function\n */\nexport interface RuleRequirement {\n /** Requirement type for programmatic checks */\n type: \"ollama\" | \"git\" | \"coverage\" | \"semantic-index\" | \"styleguide\";\n /** Human-readable description */\n description: string;\n /** Optional: how to satisfy the requirement */\n setupHint?: string;\n}\n\n/**\n * Rule migration definition for updating rule options between versions\n */\nexport interface RuleMigration {\n /** Source version (semver) */\n from: string;\n /** Target version (semver) */\n to: string;\n /** Human-readable description of what changed */\n description: string;\n /** Function to migrate options from old format to new format */\n migrate: (oldOptions: unknown[]) => unknown[];\n /** Whether this migration contains breaking changes */\n breaking?: boolean;\n}\n\n/**\n * Colocated rule metadata - exported alongside each rule\n *\n * This structure keeps all rule metadata in the same file as the rule implementation,\n * making it easy to maintain and extend as new rules are added.\n */\nexport interface RuleMeta {\n /** Rule identifier (e.g., \"consistent-dark-mode\") - must match filename */\n id: string;\n\n /** Semantic version of the rule (e.g., \"1.0.0\") */\n version: string;\n\n /** Display name for CLI (e.g., \"No Arbitrary Tailwind\") */\n name: string;\n\n /** Short description for CLI selection prompts (one line) */\n description: string;\n\n /** Default severity level */\n defaultSeverity: \"error\" | \"warn\" | \"off\";\n\n /** Category for grouping in CLI */\n category: \"static\" | \"semantic\";\n\n /** Icon for display in CLI/UI (emoji or icon name) */\n icon?: string;\n\n /** Short hint about the rule type/requirements */\n hint?: string;\n\n /** Whether rule is enabled by default during install */\n defaultEnabled?: boolean;\n\n /** External requirements the rule needs */\n requirements?: RuleRequirement[];\n\n /** Instructions to show after installation */\n postInstallInstructions?: string;\n\n /** Framework compatibility */\n frameworks?: (\"next\" | \"vite\" | \"cra\" | \"remix\")[];\n\n /** Whether this rule requires a styleguide file */\n requiresStyleguide?: boolean;\n\n /** Default options for the rule (passed as second element in ESLint config) */\n defaultOptions?: unknown[];\n\n /** Schema for prompting user to configure options during install */\n optionSchema?: RuleOptionSchema;\n\n /**\n * Detailed documentation in markdown format.\n * Should include:\n * - What the rule does\n * - Why it's useful\n * - Examples of incorrect and correct code\n * - Configuration options explained\n */\n docs: string;\n\n /**\n * Internal utility dependencies that this rule requires.\n * When the rule is copied to a target project, these utilities\n * will be transformed to import from \"uilint-eslint\" instead\n * of relative paths.\n *\n * Example: [\"coverage-aggregator\", \"dependency-graph\"]\n */\n internalDependencies?: string[];\n\n /**\n * Whether this rule is directory-based (has lib/ folder with utilities).\n * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.\n * Single-file rules are installed as single .ts files.\n *\n * When true, ESLint config imports will use:\n * ./.uilint/rules/rule-id/index.js\n * When false (default):\n * ./.uilint/rules/rule-id.js\n */\n isDirectoryBased?: boolean;\n\n /**\n * Migrations for updating rule options between versions.\n * Migrations are applied in order to transform options from older versions.\n */\n migrations?: RuleMigration[];\n\n /**\n * Which UI plugin should handle this rule.\n * Defaults based on category:\n * - \"static\" category → \"eslint\" plugin\n * - \"semantic\" category → \"semantic\" plugin\n *\n * Special cases:\n * - \"vision\" for semantic-vision rule\n */\n plugin?: \"eslint\" | \"vision\" | \"semantic\";\n\n /**\n * Custom inspector panel ID to use for this rule's issues.\n * If not specified, uses the plugin's default issue inspector.\n *\n * Examples:\n * - \"vision-issue\" for VisionIssueInspector\n * - \"duplicates\" for DuplicatesInspector\n * - \"semantic-issue\" for SemanticIssueInspector\n */\n customInspector?: string;\n\n /**\n * Custom heatmap color for this rule's issues.\n * CSS color value (hex, rgb, hsl, or named color).\n * If not specified, uses severity-based coloring.\n */\n heatmapColor?: string;\n}\n\n/**\n * Helper to define rule metadata with type safety\n */\nexport function defineRuleMeta(meta: RuleMeta): RuleMeta {\n return meta;\n}\n","/**\n * Export Resolver\n *\n * Resolves import paths and finds export definitions, following re-exports\n * to their original source files.\n */\n\nimport { ResolverFactory } from \"oxc-resolver\";\nimport { parse } from \"@typescript-eslint/typescript-estree\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { dirname, join, extname } from \"path\";\nimport type { TSESTree } from \"@typescript-eslint/utils\";\n\n// Module-level resolver instance (reused across calls)\nlet resolverInstance: ReturnType<typeof ResolverFactory.prototype.sync> | null =\n null;\nlet resolverFactory: ResolverFactory | null = null;\n\n/**\n * Information about a resolved export\n */\nexport interface ResolvedExport {\n /** The name of the export (e.g., \"Button\") */\n name: string;\n /** Absolute path to the file containing the actual definition */\n filePath: string;\n /** The local name in the source file (may differ from export name) */\n localName: string;\n /** Whether this is a re-export (export { X } from './other') */\n isReexport: boolean;\n}\n\n/**\n * Cache for file exports to avoid re-parsing\n */\nconst exportCache = new Map<\n string,\n Map<string, { localName: string; reexportSource?: string }>\n>();\n\n/**\n * Cache for parsed ASTs\n */\nconst astCache = new Map<string, TSESTree.Program>();\n\n/**\n * Cache for resolved paths\n */\nconst resolvedPathCache = new Map<string, string | null>();\n\n/**\n * Get or create the resolver factory\n */\nfunction getResolverFactory(): ResolverFactory {\n if (!resolverFactory) {\n resolverFactory = new ResolverFactory({\n extensions: [\".tsx\", \".ts\", \".jsx\", \".js\"],\n mainFields: [\"module\", \"main\"],\n conditionNames: [\"import\", \"require\", \"node\", \"default\"],\n // Enable TypeScript path resolution\n tsconfig: {\n configFile: \"tsconfig.json\",\n references: \"auto\",\n },\n });\n }\n return resolverFactory;\n}\n\n/**\n * Resolve an import path to an absolute file path\n */\nexport function resolveImportPath(\n importSource: string,\n fromFile: string\n): string | null {\n const cacheKey = `${fromFile}::${importSource}`;\n\n if (resolvedPathCache.has(cacheKey)) {\n return resolvedPathCache.get(cacheKey) ?? null;\n }\n\n // Skip node_modules\n if (\n importSource.startsWith(\"react\") ||\n importSource.startsWith(\"next\") ||\n (!importSource.startsWith(\".\") &&\n !importSource.startsWith(\"@/\") &&\n !importSource.startsWith(\"~/\"))\n ) {\n // Check if it's a known external package\n if (\n importSource.includes(\"@mui/\") ||\n importSource.includes(\"@chakra-ui/\") ||\n importSource.includes(\"antd\") ||\n importSource.includes(\"@radix-ui/\")\n ) {\n // Return a marker for external packages - we don't resolve them but track them\n resolvedPathCache.set(cacheKey, null);\n return null;\n }\n resolvedPathCache.set(cacheKey, null);\n return null;\n }\n\n try {\n const factory = getResolverFactory();\n const fromDir = dirname(fromFile);\n const result = factory.sync(fromDir, importSource);\n\n if (result.path) {\n resolvedPathCache.set(cacheKey, result.path);\n return result.path;\n }\n } catch {\n // Fallback: try manual resolution for common patterns\n const resolved = manualResolve(importSource, fromFile);\n resolvedPathCache.set(cacheKey, resolved);\n return resolved;\n }\n\n resolvedPathCache.set(cacheKey, null);\n return null;\n}\n\n/**\n * Manual fallback resolution for common patterns\n */\nfunction manualResolve(importSource: string, fromFile: string): string | null {\n const fromDir = dirname(fromFile);\n const extensions = [\".tsx\", \".ts\", \".jsx\", \".js\"];\n\n // Handle @/ alias - find tsconfig and resolve\n if (importSource.startsWith(\"@/\")) {\n const projectRoot = findProjectRoot(fromFile);\n if (projectRoot) {\n const relativePath = importSource.slice(2); // Remove @/\n for (const ext of extensions) {\n const candidate = join(projectRoot, relativePath + ext);\n if (existsSync(candidate)) {\n return candidate;\n }\n // Try index file\n const indexCandidate = join(projectRoot, relativePath, `index${ext}`);\n if (existsSync(indexCandidate)) {\n return indexCandidate;\n }\n }\n }\n }\n\n // Handle relative imports\n if (importSource.startsWith(\".\")) {\n for (const ext of extensions) {\n const candidate = join(fromDir, importSource + ext);\n if (existsSync(candidate)) {\n return candidate;\n }\n // Try index file\n const indexCandidate = join(fromDir, importSource, `index${ext}`);\n if (existsSync(indexCandidate)) {\n return indexCandidate;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Find the project root by looking for tsconfig.json or package.json\n */\nfunction findProjectRoot(fromFile: string): string | null {\n let dir = dirname(fromFile);\n const root = \"/\";\n\n while (dir !== root) {\n if (existsSync(join(dir, \"tsconfig.json\"))) {\n return dir;\n }\n if (existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\n/**\n * Parse a file and cache the AST\n */\nexport function parseFile(filePath: string): TSESTree.Program | null {\n if (astCache.has(filePath)) {\n return astCache.get(filePath)!;\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\");\n const ast = parse(content, {\n jsx: true,\n loc: true,\n range: true,\n });\n astCache.set(filePath, ast);\n return ast;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract export information from a file\n */\nfunction extractExports(\n filePath: string\n): Map<string, { localName: string; reexportSource?: string }> {\n if (exportCache.has(filePath)) {\n return exportCache.get(filePath)!;\n }\n\n const exports = new Map<\n string,\n { localName: string; reexportSource?: string }\n >();\n const ast = parseFile(filePath);\n\n if (!ast) {\n exportCache.set(filePath, exports);\n return exports;\n }\n\n for (const node of ast.body) {\n // Handle: export function Button() {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"FunctionDeclaration\" &&\n node.declaration.id\n ) {\n exports.set(node.declaration.id.name, {\n localName: node.declaration.id.name,\n });\n }\n\n // Handle: export const Button = () => {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"VariableDeclaration\"\n ) {\n for (const decl of node.declaration.declarations) {\n if (decl.id.type === \"Identifier\") {\n exports.set(decl.id.name, { localName: decl.id.name });\n }\n }\n }\n\n // Handle: export { Button } or export { Button as Btn }\n if (node.type === \"ExportNamedDeclaration\" && node.specifiers.length > 0) {\n const source = node.source?.value as string | undefined;\n for (const spec of node.specifiers) {\n if (spec.type === \"ExportSpecifier\") {\n const exportedName =\n spec.exported.type === \"Identifier\"\n ? spec.exported.name\n : spec.exported.value;\n const localName =\n spec.local.type === \"Identifier\"\n ? spec.local.name\n : spec.local.value;\n\n exports.set(exportedName, {\n localName,\n reexportSource: source,\n });\n }\n }\n }\n\n // Handle: export default function Button() {}\n if (\n node.type === \"ExportDefaultDeclaration\" &&\n node.declaration.type === \"FunctionDeclaration\" &&\n node.declaration.id\n ) {\n exports.set(\"default\", { localName: node.declaration.id.name });\n }\n\n // Handle: export default Button\n if (\n node.type === \"ExportDefaultDeclaration\" &&\n node.declaration.type === \"Identifier\"\n ) {\n exports.set(\"default\", { localName: node.declaration.name });\n }\n }\n\n exportCache.set(filePath, exports);\n return exports;\n}\n\n/**\n * Resolve an export to its original definition, following re-exports\n */\nexport function resolveExport(\n exportName: string,\n filePath: string,\n visited = new Set<string>()\n): ResolvedExport | null {\n // Cycle detection\n const key = `${filePath}::${exportName}`;\n if (visited.has(key)) {\n return null;\n }\n visited.add(key);\n\n const exports = extractExports(filePath);\n const exportInfo = exports.get(exportName);\n\n if (!exportInfo) {\n return null;\n }\n\n // If it's a re-export, follow the chain\n if (exportInfo.reexportSource) {\n const resolvedPath = resolveImportPath(exportInfo.reexportSource, filePath);\n if (resolvedPath) {\n return resolveExport(exportInfo.localName, resolvedPath, visited);\n }\n return null;\n }\n\n // This is the actual definition\n return {\n name: exportName,\n filePath,\n localName: exportInfo.localName,\n isReexport: false,\n };\n}\n\n/**\n * Clear all caches (useful for testing or watch mode)\n */\nexport function clearResolverCaches(): void {\n exportCache.clear();\n astCache.clear();\n resolvedPathCache.clear();\n}\n","/**\n * Component Parser\n *\n * Parses a single component's body to extract styling information\n * and identify nested component usage.\n */\n\nimport type { TSESTree } from \"@typescript-eslint/utils\";\nimport { parseFile } from \"./export-resolver.js\";\n\n/**\n * Known UI library import patterns\n */\nexport type LibraryName = \"shadcn\" | \"mui\" | \"chakra\" | \"antd\";\n\nexport const LIBRARY_PATTERNS: Record<LibraryName, string[]> = {\n shadcn: [\"@/components/ui\", \"@radix-ui/\", \"components/ui/\"],\n mui: [\"@mui/material\", \"@mui/icons-material\", \"@emotion/\"],\n chakra: [\"@chakra-ui/\"],\n antd: [\"antd\", \"@ant-design/\"],\n};\n\n/**\n * Information about a component used within another component\n */\nexport interface UsedComponent {\n /** Component name (e.g., \"Button\", \"Card\") */\n name: string;\n /** Import source path (e.g., \"@mui/material\", \"./button\") */\n importSource: string;\n /** Line number where the component is used */\n line: number;\n /** Column number where the component is used */\n column: number;\n}\n\n/**\n * Styling information extracted from a component\n */\nexport interface ComponentStyleInfo {\n /** Tailwind classes used in the component */\n tailwindClasses: string[];\n /** Inline style objects (as string representations) */\n inlineStyles: string[];\n /** Other components used within this component */\n usedComponents: UsedComponent[];\n /** Directly detected library (from import source) */\n directLibrary: LibraryName | null;\n}\n\n/**\n * Import map for a file: localName -> importSource\n */\nexport type ImportMap = Map<string, string>;\n\n/**\n * Extract imports from a file's AST\n */\nexport function extractImports(ast: TSESTree.Program): ImportMap {\n const imports = new Map<string, string>();\n\n for (const node of ast.body) {\n if (node.type === \"ImportDeclaration\") {\n const source = node.source.value as string;\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\") {\n imports.set(spec.local.name, source);\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n imports.set(spec.local.name, source);\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n imports.set(spec.local.name, source);\n }\n }\n }\n }\n\n return imports;\n}\n\n/**\n * Detect which UI library an import source belongs to\n */\nexport function detectLibraryFromSource(\n importSource: string\n): LibraryName | null {\n for (const [library, patterns] of Object.entries(LIBRARY_PATTERNS)) {\n if (patterns.some((p) => importSource.includes(p))) {\n return library as LibraryName;\n }\n }\n return null;\n}\n\n/**\n * Find a function/arrow function component definition by name in an AST\n */\nexport function findComponentDefinition(\n ast: TSESTree.Program,\n componentName: string\n): TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression | null {\n for (const node of ast.body) {\n // export function ComponentName() {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"FunctionDeclaration\" &&\n node.declaration.id?.name === componentName\n ) {\n return node.declaration;\n }\n\n // export const ComponentName = () => {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"VariableDeclaration\"\n ) {\n for (const decl of node.declaration.declarations) {\n if (\n decl.id.type === \"Identifier\" &&\n decl.id.name === componentName &&\n decl.init?.type === \"ArrowFunctionExpression\"\n ) {\n return decl.init;\n }\n }\n }\n\n // function ComponentName() {} (not exported directly)\n if (\n node.type === \"FunctionDeclaration\" &&\n node.id?.name === componentName\n ) {\n return node;\n }\n\n // const ComponentName = () => {}\n if (node.type === \"VariableDeclaration\") {\n for (const decl of node.declarations) {\n if (\n decl.id.type === \"Identifier\" &&\n decl.id.name === componentName &&\n decl.init?.type === \"ArrowFunctionExpression\"\n ) {\n return decl.init;\n }\n }\n }\n\n // export default function ComponentName() {}\n if (\n node.type === \"ExportDefaultDeclaration\" &&\n node.declaration.type === \"FunctionDeclaration\" &&\n node.declaration.id?.name === componentName\n ) {\n return node.declaration;\n }\n }\n\n return null;\n}\n\n/**\n * Extract Tailwind classes from a className string\n */\nfunction extractTailwindClasses(classString: string): string[] {\n return classString.split(/\\s+/).filter(Boolean);\n}\n\n/**\n * Recursively traverse a node and extract styling info\n */\nfunction traverseForStyling(\n node: TSESTree.Node,\n imports: ImportMap,\n result: ComponentStyleInfo\n): void {\n if (!node || typeof node !== \"object\") return;\n\n // Handle JSX elements\n if (node.type === \"JSXElement\" && node.openingElement) {\n const opening = node.openingElement;\n\n // Check if this is a component (PascalCase) or HTML element\n if (\n opening.name.type === \"JSXIdentifier\" &&\n /^[A-Z]/.test(opening.name.name)\n ) {\n const componentName = opening.name.name;\n const importSource = imports.get(componentName);\n\n if (importSource) {\n result.usedComponents.push({\n name: componentName,\n importSource,\n line: opening.loc.start.line,\n column: opening.loc.start.column,\n });\n\n // Check if this import is from a known UI library\n const library = detectLibraryFromSource(importSource);\n if (library && !result.directLibrary) {\n result.directLibrary = library;\n }\n }\n }\n\n // Handle JSX member expressions like Modal.Header\n if (opening.name.type === \"JSXMemberExpression\") {\n let objectName: string | null = null;\n let current = opening.name.object;\n while (current.type === \"JSXMemberExpression\") {\n current = current.object;\n }\n if (current.type === \"JSXIdentifier\") {\n objectName = current.name;\n }\n\n if (objectName) {\n const importSource = imports.get(objectName);\n if (importSource) {\n const library = detectLibraryFromSource(importSource);\n if (library && !result.directLibrary) {\n result.directLibrary = library;\n }\n }\n }\n }\n\n // Extract className/class attributes\n for (const attr of opening.attributes) {\n if (\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n (attr.name.name === \"className\" || attr.name.name === \"class\")\n ) {\n if (\n attr.value?.type === \"Literal\" &&\n typeof attr.value.value === \"string\"\n ) {\n result.tailwindClasses.push(\n ...extractTailwindClasses(attr.value.value)\n );\n }\n if (attr.value?.type === \"JSXExpressionContainer\") {\n const expr = attr.value.expression;\n if (expr.type === \"Literal\" && typeof expr.value === \"string\") {\n result.tailwindClasses.push(...extractTailwindClasses(expr.value));\n }\n if (expr.type === \"TemplateLiteral\") {\n for (const quasi of expr.quasis) {\n result.tailwindClasses.push(\n ...extractTailwindClasses(quasi.value.raw)\n );\n }\n }\n }\n }\n\n // Extract inline styles\n if (\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === \"style\"\n ) {\n if (attr.value?.type === \"JSXExpressionContainer\") {\n // Just note that there's an inline style - we don't parse the object\n result.inlineStyles.push(\"[inline style]\");\n }\n }\n }\n }\n\n // Handle cn(), clsx(), twMerge() calls\n if (\n node.type === \"CallExpression\" &&\n node.callee.type === \"Identifier\" &&\n [\"cn\", \"clsx\", \"classnames\", \"twMerge\"].includes(node.callee.name)\n ) {\n for (const arg of node.arguments) {\n if (arg.type === \"Literal\" && typeof arg.value === \"string\") {\n result.tailwindClasses.push(...extractTailwindClasses(arg.value));\n }\n if (arg.type === \"TemplateLiteral\") {\n for (const quasi of arg.quasis) {\n result.tailwindClasses.push(\n ...extractTailwindClasses(quasi.value.raw)\n );\n }\n }\n }\n }\n\n // Recursively traverse all properties\n for (const key of Object.keys(node)) {\n if (key === \"parent\" || key === \"loc\" || key === \"range\") continue;\n\n const child = (node as unknown as Record<string, unknown>)[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\") {\n traverseForStyling(item as TSESTree.Node, imports, result);\n }\n }\n } else if (child && typeof child === \"object\") {\n traverseForStyling(child as TSESTree.Node, imports, result);\n }\n }\n}\n\n/**\n * Parse a component's body and extract styling information\n */\nexport function parseComponentBody(\n filePath: string,\n componentName: string\n): ComponentStyleInfo | null {\n const ast = parseFile(filePath);\n if (!ast) return null;\n\n const imports = extractImports(ast);\n const componentDef = findComponentDefinition(ast, componentName);\n\n if (!componentDef) {\n return null;\n }\n\n const result: ComponentStyleInfo = {\n tailwindClasses: [],\n inlineStyles: [],\n usedComponents: [],\n directLibrary: null,\n };\n\n // Traverse the component body\n if (componentDef.body) {\n traverseForStyling(componentDef.body, imports, result);\n }\n\n // Deduplicate classes\n result.tailwindClasses = [...new Set(result.tailwindClasses)];\n\n return result;\n}\n\n/**\n * Analyze a file and extract all component usages with their libraries\n * (used for the entry file analysis)\n */\nexport function analyzeFileImports(\n filePath: string\n): Map<string, { importSource: string; library: LibraryName | null }> {\n const ast = parseFile(filePath);\n if (!ast) return new Map();\n\n const result = new Map<\n string,\n { importSource: string; library: LibraryName | null }\n >();\n const imports = extractImports(ast);\n\n for (const [name, source] of imports) {\n result.set(name, {\n importSource: source,\n library: detectLibraryFromSource(source),\n });\n }\n\n return result;\n}\n","/**\n * Import Graph Service\n *\n * Provides demand-driven cross-file analysis to detect UI library usage\n * across component trees. Uses in-memory caching for performance.\n */\n\nimport {\n resolveImportPath,\n resolveExport,\n clearResolverCaches,\n} from \"./export-resolver.js\";\nimport {\n parseComponentBody,\n detectLibraryFromSource,\n type LibraryName,\n type ComponentStyleInfo,\n} from \"./component-parser.js\";\n\n/**\n * Information about a component's UI library usage\n */\nexport interface ComponentLibraryInfo {\n /** Direct library (from import source, e.g., \"@mui/material\" -> \"mui\") */\n library: LibraryName | null;\n /** Libraries used internally by this component (for local components) */\n internalLibraries: Set<LibraryName>;\n /** Evidence of which internal components caused the library detection */\n libraryEvidence: Array<{\n componentName: string;\n library: LibraryName;\n }>;\n /** Whether this is a local component (resolved from project files) */\n isLocalComponent: boolean;\n}\n\n/**\n * Cache for analyzed components: \"filePath::componentName\" -> ComponentLibraryInfo\n */\nconst componentLibraryCache = new Map<string, ComponentLibraryInfo>();\n\n/**\n * Get a singleton instance of the import graph service\n */\nexport function getImportGraphService() {\n return {\n getComponentLibrary,\n clearCache,\n };\n}\n\n/**\n * Analyze a component's library usage, including transitive dependencies\n *\n * @param contextFilePath - The file where the component is used (for resolving relative imports)\n * @param componentName - The name of the component (e.g., \"Button\", \"MyCard\")\n * @param importSource - The import source (e.g., \"@mui/material\", \"./components/cards\")\n * @returns Library information including direct and transitive library usage\n */\nexport function getComponentLibrary(\n contextFilePath: string,\n componentName: string,\n importSource: string\n): ComponentLibraryInfo {\n // Check if import source directly indicates a known library\n const directLibrary = detectLibraryFromSource(importSource);\n\n if (directLibrary) {\n // It's a direct import from a known library (e.g., @mui/material)\n return {\n library: directLibrary,\n internalLibraries: new Set(),\n libraryEvidence: [],\n isLocalComponent: false,\n };\n }\n\n // It's a local component - resolve and analyze it\n const resolvedPath = resolveImportPath(importSource, contextFilePath);\n\n if (!resolvedPath) {\n // Could not resolve - might be external or invalid\n return {\n library: null,\n internalLibraries: new Set(),\n libraryEvidence: [],\n isLocalComponent: false,\n };\n }\n\n // Check cache\n const cacheKey = `${resolvedPath}::${componentName}`;\n if (componentLibraryCache.has(cacheKey)) {\n return componentLibraryCache.get(cacheKey)!;\n }\n\n // Resolve re-exports to find the actual component definition\n const resolvedExport = resolveExport(componentName, resolvedPath);\n const actualFilePath = resolvedExport?.filePath ?? resolvedPath;\n const actualComponentName = resolvedExport?.localName ?? componentName;\n\n // Analyze the component body\n const result = analyzeComponentLibraries(\n actualFilePath,\n actualComponentName,\n new Set([cacheKey]) // Track visited to prevent cycles\n );\n\n componentLibraryCache.set(cacheKey, result);\n return result;\n}\n\n/**\n * Recursively analyze a component's library usage\n */\nfunction analyzeComponentLibraries(\n filePath: string,\n componentName: string,\n visited: Set<string>\n): ComponentLibraryInfo {\n const styleInfo = parseComponentBody(filePath, componentName);\n\n if (!styleInfo) {\n return {\n library: null,\n internalLibraries: new Set(),\n libraryEvidence: [],\n isLocalComponent: true,\n };\n }\n\n const internalLibraries = new Set<LibraryName>();\n const libraryEvidence: Array<{\n componentName: string;\n library: LibraryName;\n }> = [];\n\n // Check direct library usage within this component\n if (styleInfo.directLibrary) {\n internalLibraries.add(styleInfo.directLibrary);\n }\n\n // Analyze each used component\n for (const usedComponent of styleInfo.usedComponents) {\n const usedLibrary = detectLibraryFromSource(usedComponent.importSource);\n\n if (usedLibrary) {\n // Direct import from a known library\n internalLibraries.add(usedLibrary);\n libraryEvidence.push({\n componentName: usedComponent.name,\n library: usedLibrary,\n });\n } else {\n // It's a local component - recurse into it\n const resolvedPath = resolveImportPath(\n usedComponent.importSource,\n filePath\n );\n\n if (resolvedPath) {\n const cacheKey = `${resolvedPath}::${usedComponent.name}`;\n\n // Skip if already visited (cycle detection)\n if (!visited.has(cacheKey)) {\n visited.add(cacheKey);\n\n // Check cache first\n let nestedInfo: ComponentLibraryInfo;\n if (componentLibraryCache.has(cacheKey)) {\n nestedInfo = componentLibraryCache.get(cacheKey)!;\n } else {\n // Resolve re-exports\n const resolvedExport = resolveExport(\n usedComponent.name,\n resolvedPath\n );\n const actualFilePath = resolvedExport?.filePath ?? resolvedPath;\n const actualComponentName =\n resolvedExport?.localName ?? usedComponent.name;\n\n nestedInfo = analyzeComponentLibraries(\n actualFilePath,\n actualComponentName,\n visited\n );\n componentLibraryCache.set(cacheKey, nestedInfo);\n }\n\n // Aggregate the nested component's libraries\n if (nestedInfo.library) {\n internalLibraries.add(nestedInfo.library);\n libraryEvidence.push({\n componentName: usedComponent.name,\n library: nestedInfo.library,\n });\n }\n\n for (const lib of nestedInfo.internalLibraries) {\n internalLibraries.add(lib);\n }\n\n // Add evidence from nested components\n for (const evidence of nestedInfo.libraryEvidence) {\n libraryEvidence.push({\n componentName: `${usedComponent.name} → ${evidence.componentName}`,\n library: evidence.library,\n });\n }\n }\n }\n }\n }\n\n return {\n library: styleInfo.directLibrary,\n internalLibraries,\n libraryEvidence,\n isLocalComponent: true,\n };\n}\n\n/**\n * Clear all caches (useful for testing or between ESLint runs)\n */\nexport function clearCache(): void {\n componentLibraryCache.clear();\n clearResolverCaches();\n}\n\n// Re-export types for convenience\nexport type { LibraryName };\n","/**\n * Rule: no-mixed-component-libraries\n *\n * Forbids using non-preferred UI component libraries. Reports errors at\n * the JSX usage site, including transitive usage through local components.\n *\n * Examples:\n * - <MuiButton> from @mui/material -> error at <MuiButton> usage\n * - <MyCard> that internally uses MUI -> error at <MyCard> usage\n */\n\nimport type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule, defineRuleMeta } from \"../../utils/create-rule.js\";\nimport {\n getComponentLibrary,\n type LibraryName,\n} from \"./lib/import-graph.js\";\n\ntype MessageIds = \"nonPreferredLibrary\" | \"transitiveNonPreferred\";\ntype Options = [\n {\n /** The preferred UI library. Components from other libraries will be flagged. */\n preferred: LibraryName;\n /** Additional libraries to detect (defaults to common ones) */\n libraries?: LibraryName[];\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"no-mixed-component-libraries\",\n version: \"1.0.0\",\n name: \"No Mixed Component Libraries\",\n description: \"Forbid mixing component libraries (e.g., shadcn + MUI)\",\n defaultSeverity: \"error\",\n category: \"static\",\n icon: \"🧩\",\n hint: \"Ensures consistent UI library usage\",\n defaultEnabled: true,\n isDirectoryBased: true,\n defaultOptions: [{ preferred: \"shadcn\", libraries: [\"shadcn\", \"mui\"] }],\n optionSchema: {\n fields: [\n {\n key: \"preferred\",\n label: \"Preferred component library\",\n type: \"select\",\n defaultValue: \"shadcn\",\n options: [\n { value: \"shadcn\", label: \"shadcn/ui\" },\n { value: \"mui\", label: \"MUI (Material-UI)\" },\n { value: \"chakra\", label: \"Chakra UI\" },\n { value: \"antd\", label: \"Ant Design\" },\n ],\n description: \"The preferred UI library. Components from other libraries will be flagged.\",\n },\n ],\n },\n docs: `\n## What it does\n\nDetects and reports when components from non-preferred UI libraries are used in your codebase.\nThis includes both direct imports and transitive usage through your own components that wrap\nnon-preferred libraries internally.\n\n## Why it's useful\n\n- **Consistency**: Ensures a uniform look and feel across your application\n- **Bundle size**: Prevents accidentally including multiple UI frameworks\n- **Maintenance**: Reduces the number of styling systems to maintain\n- **Migration support**: Helps identify what needs to change when migrating UI libraries\n\n## Examples\n\n### ❌ Incorrect (with preferred: \"shadcn\")\n\n\\`\\`\\`tsx\n// Direct MUI usage\nimport { Button } from '@mui/material';\n<Button>Click me</Button> // Error: Component <Button> is from mui\n\n// Transitive usage through local component\nimport { MyCard } from './components/MyCard'; // MyCard uses MUI internally\n<MyCard /> // Error: Component <MyCard> internally uses mui components\n\\`\\`\\`\n\n### ✅ Correct\n\n\\`\\`\\`tsx\n// Using the preferred library\nimport { Button } from '@/components/ui/button';\nimport { Card } from '@/components/ui/card';\n\n<Button>Click me</Button>\n<Card>Content</Card>\n\\`\\`\\`\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/no-mixed-component-libraries\": [\"error\", {\n preferred: \"shadcn\", // Your preferred library\n libraries: [\"shadcn\", \"mui\", \"chakra\", \"antd\"] // Libraries to detect\n}]\n\\`\\`\\`\n\n## Supported Libraries\n\n- **shadcn**: shadcn/ui components (imports from \\`@/components/ui/\\`)\n- **mui**: Material-UI (\\`@mui/material\\`, \\`@mui/joy\\`)\n- **chakra**: Chakra UI (\\`@chakra-ui/react\\`)\n- **antd**: Ant Design (\\`antd\\`)\n`,\n\n});\n\n/**\n * Information about a component usage in the file\n */\ninterface ComponentUsage {\n /** The JSX element node (for error reporting) */\n node: TSESTree.JSXOpeningElement;\n /** Component name (e.g., \"Button\", \"MuiCard\") */\n componentName: string;\n /** Import source (e.g., \"@mui/material\", \"./components/cards\") */\n importSource: string;\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"no-mixed-component-libraries\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Forbid using non-preferred UI component libraries. Reports at JSX usage sites, including transitive usage.\",\n },\n messages: {\n nonPreferredLibrary:\n \"Component <{{component}}> is from {{library}}, but {{preferred}} is the preferred library.\",\n transitiveNonPreferred:\n \"Component <{{component}}> internally uses {{libraries}} components ({{internalComponents}}). The preferred library is {{preferred}}.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n preferred: {\n type: \"string\",\n enum: [\"shadcn\", \"mui\", \"chakra\", \"antd\"],\n description: \"The preferred UI library\",\n },\n libraries: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"shadcn\", \"mui\", \"chakra\", \"antd\"],\n },\n description: \"Libraries to detect (defaults to all)\",\n },\n },\n required: [\"preferred\"],\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [\n { preferred: \"shadcn\", libraries: [\"shadcn\", \"mui\", \"chakra\", \"antd\"] },\n ],\n create(context) {\n const options = context.options[0];\n const preferred = options.preferred;\n\n // Track imports: localName -> importSource\n const importMap = new Map<string, string>();\n\n // Track all component usages for analysis at Program:exit\n const componentUsages: ComponentUsage[] = [];\n\n return {\n ImportDeclaration(node) {\n const source = node.source.value as string;\n\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\") {\n importMap.set(spec.local.name, source);\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n importMap.set(spec.local.name, source);\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n importMap.set(spec.local.name, source);\n }\n }\n },\n\n JSXOpeningElement(node) {\n // Get the component name\n let componentName: string | null = null;\n\n if (node.name.type === \"JSXIdentifier\") {\n componentName = node.name.name;\n } else if (node.name.type === \"JSXMemberExpression\") {\n // Handle Namespace.Component (e.g., Modal.Header)\n let current = node.name.object;\n while (current.type === \"JSXMemberExpression\") {\n current = current.object;\n }\n if (current.type === \"JSXIdentifier\") {\n componentName = current.name;\n }\n }\n\n // Skip HTML elements (lowercase) and missing names\n if (!componentName || !/^[A-Z]/.test(componentName)) {\n return;\n }\n\n // Skip components that aren't imported (might be defined in same file)\n const importSource = importMap.get(componentName);\n if (!importSource) {\n return;\n }\n\n componentUsages.push({\n node,\n componentName,\n importSource,\n });\n },\n\n \"Program:exit\"() {\n const filename = context.filename || context.getFilename();\n\n for (const usage of componentUsages) {\n const libraryInfo = getComponentLibrary(\n filename,\n usage.componentName,\n usage.importSource\n );\n\n // Case 1: Direct import from non-preferred library\n if (libraryInfo.library && libraryInfo.library !== preferred) {\n context.report({\n node: usage.node,\n messageId: \"nonPreferredLibrary\",\n data: {\n component: usage.componentName,\n library: libraryInfo.library,\n preferred,\n },\n });\n continue;\n }\n\n // Case 2: Local component that uses non-preferred library internally\n if (\n libraryInfo.isLocalComponent &&\n libraryInfo.internalLibraries.size > 0\n ) {\n const nonPreferredLibs = [...libraryInfo.internalLibraries].filter(\n (lib) => lib !== preferred\n );\n\n if (nonPreferredLibs.length > 0) {\n // Get evidence of which internal components caused the violation\n const internalComponents = libraryInfo.libraryEvidence\n .filter((e) => e.library !== preferred)\n .map((e) => e.componentName)\n .slice(0, 3) // Limit to first 3 for readability\n .join(\", \");\n\n context.report({\n node: usage.node,\n messageId: \"transitiveNonPreferred\",\n data: {\n component: usage.componentName,\n libraries: nonPreferredLibs.join(\", \"),\n internalComponents: internalComponents || \"unknown\",\n preferred,\n },\n });\n }\n }\n }\n },\n };\n },\n});\n"],"mappings":"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","names":["meta"]}
1
+ {"version":3,"sources":["../../src/utils/create-rule.ts","../../src/rules/no-mixed-component-libraries/lib/export-resolver.ts","../../src/rules/no-mixed-component-libraries/lib/component-parser.ts","../../src/rules/no-mixed-component-libraries/lib/import-graph.ts","../../src/rules/no-mixed-component-libraries/index.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\n/**\n * Schema for prompting user to configure a rule option in the CLI\n */\nexport interface OptionFieldSchema {\n /** Field name in the options object */\n key: string;\n /** Display label for the prompt */\n label: string;\n /** Prompt type */\n type: \"text\" | \"number\" | \"boolean\" | \"select\" | \"multiselect\";\n /** Default value */\n defaultValue: unknown;\n /** Placeholder text (for text/number inputs) */\n placeholder?: string;\n /** Options for select/multiselect */\n options?: Array<{ value: string | number; label: string }>;\n /** Description/hint for the field */\n description?: string;\n}\n\n/**\n * Schema describing how to prompt for rule options during installation\n */\nexport interface RuleOptionSchema {\n /** Fields that can be configured for this rule */\n fields: OptionFieldSchema[];\n}\n\n/**\n * External requirement that a rule needs to function\n */\nexport interface RuleRequirement {\n /** Requirement type for programmatic checks */\n type: \"ollama\" | \"git\" | \"coverage\" | \"semantic-index\" | \"styleguide\";\n /** Human-readable description */\n description: string;\n /** Optional: how to satisfy the requirement */\n setupHint?: string;\n}\n\n/**\n * Rule migration definition for updating rule options between versions\n */\nexport interface RuleMigration {\n /** Source version (semver) */\n from: string;\n /** Target version (semver) */\n to: string;\n /** Human-readable description of what changed */\n description: string;\n /** Function to migrate options from old format to new format */\n migrate: (oldOptions: unknown[]) => unknown[];\n /** Whether this migration contains breaking changes */\n breaking?: boolean;\n}\n\n/**\n * Colocated rule metadata - exported alongside each rule\n *\n * This structure keeps all rule metadata in the same file as the rule implementation,\n * making it easy to maintain and extend as new rules are added.\n */\nexport interface RuleMeta {\n /** Rule identifier (e.g., \"consistent-dark-mode\") - must match filename */\n id: string;\n\n /** Semantic version of the rule (e.g., \"1.0.0\") */\n version: string;\n\n /** Display name for CLI (e.g., \"No Arbitrary Tailwind\") */\n name: string;\n\n /** Short description for CLI selection prompts (one line) */\n description: string;\n\n /** Default severity level */\n defaultSeverity: \"error\" | \"warn\" | \"off\";\n\n /** Category for grouping in CLI */\n category: \"static\" | \"semantic\";\n\n /** Icon for display in CLI/UI (emoji or icon name) */\n icon?: string;\n\n /** Short hint about the rule type/requirements */\n hint?: string;\n\n /** Whether rule is enabled by default during install */\n defaultEnabled?: boolean;\n\n /** External requirements the rule needs */\n requirements?: RuleRequirement[];\n\n /**\n * NPM packages that must be installed for this rule to work.\n * These will be added to the target project's dependencies during installation.\n *\n * Example: [\"xxhash-wasm\"] for rules using the xxhash library\n */\n npmDependencies?: string[];\n\n /** Instructions to show after installation */\n postInstallInstructions?: string;\n\n /** Framework compatibility */\n frameworks?: (\"next\" | \"vite\" | \"cra\" | \"remix\")[];\n\n /** Whether this rule requires a styleguide file */\n requiresStyleguide?: boolean;\n\n /** Default options for the rule (passed as second element in ESLint config) */\n defaultOptions?: unknown[];\n\n /** Schema for prompting user to configure options during install */\n optionSchema?: RuleOptionSchema;\n\n /**\n * Detailed documentation in markdown format.\n * Should include:\n * - What the rule does\n * - Why it's useful\n * - Examples of incorrect and correct code\n * - Configuration options explained\n */\n docs: string;\n\n /**\n * Internal utility dependencies that this rule requires.\n * When the rule is copied to a target project, these utilities\n * will be transformed to import from \"uilint-eslint\" instead\n * of relative paths.\n *\n * Example: [\"coverage-aggregator\", \"dependency-graph\"]\n */\n internalDependencies?: string[];\n\n /**\n * Whether this rule is directory-based (has lib/ folder with utilities).\n * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.\n * Single-file rules are installed as single .ts files.\n *\n * When true, ESLint config imports will use:\n * ./.uilint/rules/rule-id/index.js\n * When false (default):\n * ./.uilint/rules/rule-id.js\n */\n isDirectoryBased?: boolean;\n\n /**\n * Migrations for updating rule options between versions.\n * Migrations are applied in order to transform options from older versions.\n */\n migrations?: RuleMigration[];\n\n /**\n * Which UI plugin should handle this rule.\n * Defaults based on category:\n * - \"static\" category → \"eslint\" plugin\n * - \"semantic\" category → \"semantic\" plugin\n *\n * Special cases:\n * - \"vision\" for semantic-vision rule\n */\n plugin?: \"eslint\" | \"vision\" | \"semantic\";\n\n /**\n * Custom inspector panel ID to use for this rule's issues.\n * If not specified, uses the plugin's default issue inspector.\n *\n * Examples:\n * - \"vision-issue\" for VisionIssueInspector\n * - \"duplicates\" for DuplicatesInspector\n * - \"semantic-issue\" for SemanticIssueInspector\n */\n customInspector?: string;\n\n /**\n * Custom heatmap color for this rule's issues.\n * CSS color value (hex, rgb, hsl, or named color).\n * If not specified, uses severity-based coloring.\n */\n heatmapColor?: string;\n}\n\n/**\n * Helper to define rule metadata with type safety\n */\nexport function defineRuleMeta(meta: RuleMeta): RuleMeta {\n return meta;\n}\n","/**\n * Export Resolver\n *\n * Resolves import paths and finds export definitions, following re-exports\n * to their original source files.\n */\n\nimport { ResolverFactory } from \"oxc-resolver\";\nimport { parse } from \"@typescript-eslint/typescript-estree\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { dirname, join, extname } from \"path\";\nimport type { TSESTree } from \"@typescript-eslint/utils\";\n\n// Module-level resolver instance (reused across calls)\nlet resolverInstance: ReturnType<typeof ResolverFactory.prototype.sync> | null =\n null;\nlet resolverFactory: ResolverFactory | null = null;\n\n/**\n * Information about a resolved export\n */\nexport interface ResolvedExport {\n /** The name of the export (e.g., \"Button\") */\n name: string;\n /** Absolute path to the file containing the actual definition */\n filePath: string;\n /** The local name in the source file (may differ from export name) */\n localName: string;\n /** Whether this is a re-export (export { X } from './other') */\n isReexport: boolean;\n}\n\n/**\n * Cache for file exports to avoid re-parsing\n */\nconst exportCache = new Map<\n string,\n Map<string, { localName: string; reexportSource?: string }>\n>();\n\n/**\n * Cache for parsed ASTs\n */\nconst astCache = new Map<string, TSESTree.Program>();\n\n/**\n * Cache for resolved paths\n */\nconst resolvedPathCache = new Map<string, string | null>();\n\n/**\n * Get or create the resolver factory\n */\nfunction getResolverFactory(): ResolverFactory {\n if (!resolverFactory) {\n resolverFactory = new ResolverFactory({\n extensions: [\".tsx\", \".ts\", \".jsx\", \".js\"],\n mainFields: [\"module\", \"main\"],\n conditionNames: [\"import\", \"require\", \"node\", \"default\"],\n // Enable TypeScript path resolution\n tsconfig: {\n configFile: \"tsconfig.json\",\n references: \"auto\",\n },\n });\n }\n return resolverFactory;\n}\n\n/**\n * Resolve an import path to an absolute file path\n */\nexport function resolveImportPath(\n importSource: string,\n fromFile: string\n): string | null {\n const cacheKey = `${fromFile}::${importSource}`;\n\n if (resolvedPathCache.has(cacheKey)) {\n return resolvedPathCache.get(cacheKey) ?? null;\n }\n\n // Skip node_modules\n if (\n importSource.startsWith(\"react\") ||\n importSource.startsWith(\"next\") ||\n (!importSource.startsWith(\".\") &&\n !importSource.startsWith(\"@/\") &&\n !importSource.startsWith(\"~/\"))\n ) {\n // Check if it's a known external package\n if (\n importSource.includes(\"@mui/\") ||\n importSource.includes(\"@chakra-ui/\") ||\n importSource.includes(\"antd\") ||\n importSource.includes(\"@radix-ui/\")\n ) {\n // Return a marker for external packages - we don't resolve them but track them\n resolvedPathCache.set(cacheKey, null);\n return null;\n }\n resolvedPathCache.set(cacheKey, null);\n return null;\n }\n\n try {\n const factory = getResolverFactory();\n const fromDir = dirname(fromFile);\n const result = factory.sync(fromDir, importSource);\n\n if (result.path) {\n resolvedPathCache.set(cacheKey, result.path);\n return result.path;\n }\n } catch {\n // Fallback: try manual resolution for common patterns\n const resolved = manualResolve(importSource, fromFile);\n resolvedPathCache.set(cacheKey, resolved);\n return resolved;\n }\n\n resolvedPathCache.set(cacheKey, null);\n return null;\n}\n\n/**\n * Manual fallback resolution for common patterns\n */\nfunction manualResolve(importSource: string, fromFile: string): string | null {\n const fromDir = dirname(fromFile);\n const extensions = [\".tsx\", \".ts\", \".jsx\", \".js\"];\n\n // Handle @/ alias - find tsconfig and resolve\n if (importSource.startsWith(\"@/\")) {\n const projectRoot = findProjectRoot(fromFile);\n if (projectRoot) {\n const relativePath = importSource.slice(2); // Remove @/\n for (const ext of extensions) {\n const candidate = join(projectRoot, relativePath + ext);\n if (existsSync(candidate)) {\n return candidate;\n }\n // Try index file\n const indexCandidate = join(projectRoot, relativePath, `index${ext}`);\n if (existsSync(indexCandidate)) {\n return indexCandidate;\n }\n }\n }\n }\n\n // Handle relative imports\n if (importSource.startsWith(\".\")) {\n for (const ext of extensions) {\n const candidate = join(fromDir, importSource + ext);\n if (existsSync(candidate)) {\n return candidate;\n }\n // Try index file\n const indexCandidate = join(fromDir, importSource, `index${ext}`);\n if (existsSync(indexCandidate)) {\n return indexCandidate;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Find the project root by looking for tsconfig.json or package.json\n */\nfunction findProjectRoot(fromFile: string): string | null {\n let dir = dirname(fromFile);\n const root = \"/\";\n\n while (dir !== root) {\n if (existsSync(join(dir, \"tsconfig.json\"))) {\n return dir;\n }\n if (existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\n/**\n * Parse a file and cache the AST\n */\nexport function parseFile(filePath: string): TSESTree.Program | null {\n if (astCache.has(filePath)) {\n return astCache.get(filePath)!;\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\");\n const ast = parse(content, {\n jsx: true,\n loc: true,\n range: true,\n });\n astCache.set(filePath, ast);\n return ast;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract export information from a file\n */\nfunction extractExports(\n filePath: string\n): Map<string, { localName: string; reexportSource?: string }> {\n if (exportCache.has(filePath)) {\n return exportCache.get(filePath)!;\n }\n\n const exports = new Map<\n string,\n { localName: string; reexportSource?: string }\n >();\n const ast = parseFile(filePath);\n\n if (!ast) {\n exportCache.set(filePath, exports);\n return exports;\n }\n\n for (const node of ast.body) {\n // Handle: export function Button() {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"FunctionDeclaration\" &&\n node.declaration.id\n ) {\n exports.set(node.declaration.id.name, {\n localName: node.declaration.id.name,\n });\n }\n\n // Handle: export const Button = () => {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"VariableDeclaration\"\n ) {\n for (const decl of node.declaration.declarations) {\n if (decl.id.type === \"Identifier\") {\n exports.set(decl.id.name, { localName: decl.id.name });\n }\n }\n }\n\n // Handle: export { Button } or export { Button as Btn }\n if (node.type === \"ExportNamedDeclaration\" && node.specifiers.length > 0) {\n const source = node.source?.value as string | undefined;\n for (const spec of node.specifiers) {\n if (spec.type === \"ExportSpecifier\") {\n const exportedName =\n spec.exported.type === \"Identifier\"\n ? spec.exported.name\n : spec.exported.value;\n const localName =\n spec.local.type === \"Identifier\"\n ? spec.local.name\n : spec.local.value;\n\n exports.set(exportedName, {\n localName,\n reexportSource: source,\n });\n }\n }\n }\n\n // Handle: export default function Button() {}\n if (\n node.type === \"ExportDefaultDeclaration\" &&\n node.declaration.type === \"FunctionDeclaration\" &&\n node.declaration.id\n ) {\n exports.set(\"default\", { localName: node.declaration.id.name });\n }\n\n // Handle: export default Button\n if (\n node.type === \"ExportDefaultDeclaration\" &&\n node.declaration.type === \"Identifier\"\n ) {\n exports.set(\"default\", { localName: node.declaration.name });\n }\n }\n\n exportCache.set(filePath, exports);\n return exports;\n}\n\n/**\n * Resolve an export to its original definition, following re-exports\n */\nexport function resolveExport(\n exportName: string,\n filePath: string,\n visited = new Set<string>()\n): ResolvedExport | null {\n // Cycle detection\n const key = `${filePath}::${exportName}`;\n if (visited.has(key)) {\n return null;\n }\n visited.add(key);\n\n const exports = extractExports(filePath);\n const exportInfo = exports.get(exportName);\n\n if (!exportInfo) {\n return null;\n }\n\n // If it's a re-export, follow the chain\n if (exportInfo.reexportSource) {\n const resolvedPath = resolveImportPath(exportInfo.reexportSource, filePath);\n if (resolvedPath) {\n return resolveExport(exportInfo.localName, resolvedPath, visited);\n }\n return null;\n }\n\n // This is the actual definition\n return {\n name: exportName,\n filePath,\n localName: exportInfo.localName,\n isReexport: false,\n };\n}\n\n/**\n * Clear all caches (useful for testing or watch mode)\n */\nexport function clearResolverCaches(): void {\n exportCache.clear();\n astCache.clear();\n resolvedPathCache.clear();\n}\n","/**\n * Component Parser\n *\n * Parses a single component's body to extract styling information\n * and identify nested component usage.\n */\n\nimport type { TSESTree } from \"@typescript-eslint/utils\";\nimport { parseFile } from \"./export-resolver.js\";\n\n/**\n * Known UI library import patterns\n */\nexport type LibraryName = \"shadcn\" | \"mui\" | \"chakra\" | \"antd\";\n\nexport const LIBRARY_PATTERNS: Record<LibraryName, string[]> = {\n shadcn: [\"@/components/ui\", \"@radix-ui/\", \"components/ui/\"],\n mui: [\"@mui/material\", \"@mui/icons-material\", \"@emotion/\"],\n chakra: [\"@chakra-ui/\"],\n antd: [\"antd\", \"@ant-design/\"],\n};\n\n/**\n * Information about a component used within another component\n */\nexport interface UsedComponent {\n /** Component name (e.g., \"Button\", \"Card\") */\n name: string;\n /** Import source path (e.g., \"@mui/material\", \"./button\") */\n importSource: string;\n /** Line number where the component is used */\n line: number;\n /** Column number where the component is used */\n column: number;\n}\n\n/**\n * Styling information extracted from a component\n */\nexport interface ComponentStyleInfo {\n /** Tailwind classes used in the component */\n tailwindClasses: string[];\n /** Inline style objects (as string representations) */\n inlineStyles: string[];\n /** Other components used within this component */\n usedComponents: UsedComponent[];\n /** Directly detected library (from import source) */\n directLibrary: LibraryName | null;\n}\n\n/**\n * Import map for a file: localName -> importSource\n */\nexport type ImportMap = Map<string, string>;\n\n/**\n * Extract imports from a file's AST\n */\nexport function extractImports(ast: TSESTree.Program): ImportMap {\n const imports = new Map<string, string>();\n\n for (const node of ast.body) {\n if (node.type === \"ImportDeclaration\") {\n const source = node.source.value as string;\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\") {\n imports.set(spec.local.name, source);\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n imports.set(spec.local.name, source);\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n imports.set(spec.local.name, source);\n }\n }\n }\n }\n\n return imports;\n}\n\n/**\n * Detect which UI library an import source belongs to\n */\nexport function detectLibraryFromSource(\n importSource: string\n): LibraryName | null {\n for (const [library, patterns] of Object.entries(LIBRARY_PATTERNS)) {\n if (patterns.some((p) => importSource.includes(p))) {\n return library as LibraryName;\n }\n }\n return null;\n}\n\n/**\n * Find a function/arrow function component definition by name in an AST\n */\nexport function findComponentDefinition(\n ast: TSESTree.Program,\n componentName: string\n): TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression | null {\n for (const node of ast.body) {\n // export function ComponentName() {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"FunctionDeclaration\" &&\n node.declaration.id?.name === componentName\n ) {\n return node.declaration;\n }\n\n // export const ComponentName = () => {}\n if (\n node.type === \"ExportNamedDeclaration\" &&\n node.declaration?.type === \"VariableDeclaration\"\n ) {\n for (const decl of node.declaration.declarations) {\n if (\n decl.id.type === \"Identifier\" &&\n decl.id.name === componentName &&\n decl.init?.type === \"ArrowFunctionExpression\"\n ) {\n return decl.init;\n }\n }\n }\n\n // function ComponentName() {} (not exported directly)\n if (\n node.type === \"FunctionDeclaration\" &&\n node.id?.name === componentName\n ) {\n return node;\n }\n\n // const ComponentName = () => {}\n if (node.type === \"VariableDeclaration\") {\n for (const decl of node.declarations) {\n if (\n decl.id.type === \"Identifier\" &&\n decl.id.name === componentName &&\n decl.init?.type === \"ArrowFunctionExpression\"\n ) {\n return decl.init;\n }\n }\n }\n\n // export default function ComponentName() {}\n if (\n node.type === \"ExportDefaultDeclaration\" &&\n node.declaration.type === \"FunctionDeclaration\" &&\n node.declaration.id?.name === componentName\n ) {\n return node.declaration;\n }\n }\n\n return null;\n}\n\n/**\n * Extract Tailwind classes from a className string\n */\nfunction extractTailwindClasses(classString: string): string[] {\n return classString.split(/\\s+/).filter(Boolean);\n}\n\n/**\n * Recursively traverse a node and extract styling info\n */\nfunction traverseForStyling(\n node: TSESTree.Node,\n imports: ImportMap,\n result: ComponentStyleInfo\n): void {\n if (!node || typeof node !== \"object\") return;\n\n // Handle JSX elements\n if (node.type === \"JSXElement\" && node.openingElement) {\n const opening = node.openingElement;\n\n // Check if this is a component (PascalCase) or HTML element\n if (\n opening.name.type === \"JSXIdentifier\" &&\n /^[A-Z]/.test(opening.name.name)\n ) {\n const componentName = opening.name.name;\n const importSource = imports.get(componentName);\n\n if (importSource) {\n result.usedComponents.push({\n name: componentName,\n importSource,\n line: opening.loc.start.line,\n column: opening.loc.start.column,\n });\n\n // Check if this import is from a known UI library\n const library = detectLibraryFromSource(importSource);\n if (library && !result.directLibrary) {\n result.directLibrary = library;\n }\n }\n }\n\n // Handle JSX member expressions like Modal.Header\n if (opening.name.type === \"JSXMemberExpression\") {\n let objectName: string | null = null;\n let current = opening.name.object;\n while (current.type === \"JSXMemberExpression\") {\n current = current.object;\n }\n if (current.type === \"JSXIdentifier\") {\n objectName = current.name;\n }\n\n if (objectName) {\n const importSource = imports.get(objectName);\n if (importSource) {\n const library = detectLibraryFromSource(importSource);\n if (library && !result.directLibrary) {\n result.directLibrary = library;\n }\n }\n }\n }\n\n // Extract className/class attributes\n for (const attr of opening.attributes) {\n if (\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n (attr.name.name === \"className\" || attr.name.name === \"class\")\n ) {\n if (\n attr.value?.type === \"Literal\" &&\n typeof attr.value.value === \"string\"\n ) {\n result.tailwindClasses.push(\n ...extractTailwindClasses(attr.value.value)\n );\n }\n if (attr.value?.type === \"JSXExpressionContainer\") {\n const expr = attr.value.expression;\n if (expr.type === \"Literal\" && typeof expr.value === \"string\") {\n result.tailwindClasses.push(...extractTailwindClasses(expr.value));\n }\n if (expr.type === \"TemplateLiteral\") {\n for (const quasi of expr.quasis) {\n result.tailwindClasses.push(\n ...extractTailwindClasses(quasi.value.raw)\n );\n }\n }\n }\n }\n\n // Extract inline styles\n if (\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === \"style\"\n ) {\n if (attr.value?.type === \"JSXExpressionContainer\") {\n // Just note that there's an inline style - we don't parse the object\n result.inlineStyles.push(\"[inline style]\");\n }\n }\n }\n }\n\n // Handle cn(), clsx(), twMerge() calls\n if (\n node.type === \"CallExpression\" &&\n node.callee.type === \"Identifier\" &&\n [\"cn\", \"clsx\", \"classnames\", \"twMerge\"].includes(node.callee.name)\n ) {\n for (const arg of node.arguments) {\n if (arg.type === \"Literal\" && typeof arg.value === \"string\") {\n result.tailwindClasses.push(...extractTailwindClasses(arg.value));\n }\n if (arg.type === \"TemplateLiteral\") {\n for (const quasi of arg.quasis) {\n result.tailwindClasses.push(\n ...extractTailwindClasses(quasi.value.raw)\n );\n }\n }\n }\n }\n\n // Recursively traverse all properties\n for (const key of Object.keys(node)) {\n if (key === \"parent\" || key === \"loc\" || key === \"range\") continue;\n\n const child = (node as unknown as Record<string, unknown>)[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\") {\n traverseForStyling(item as TSESTree.Node, imports, result);\n }\n }\n } else if (child && typeof child === \"object\") {\n traverseForStyling(child as TSESTree.Node, imports, result);\n }\n }\n}\n\n/**\n * Parse a component's body and extract styling information\n */\nexport function parseComponentBody(\n filePath: string,\n componentName: string\n): ComponentStyleInfo | null {\n const ast = parseFile(filePath);\n if (!ast) return null;\n\n const imports = extractImports(ast);\n const componentDef = findComponentDefinition(ast, componentName);\n\n if (!componentDef) {\n return null;\n }\n\n const result: ComponentStyleInfo = {\n tailwindClasses: [],\n inlineStyles: [],\n usedComponents: [],\n directLibrary: null,\n };\n\n // Traverse the component body\n if (componentDef.body) {\n traverseForStyling(componentDef.body, imports, result);\n }\n\n // Deduplicate classes\n result.tailwindClasses = [...new Set(result.tailwindClasses)];\n\n return result;\n}\n\n/**\n * Analyze a file and extract all component usages with their libraries\n * (used for the entry file analysis)\n */\nexport function analyzeFileImports(\n filePath: string\n): Map<string, { importSource: string; library: LibraryName | null }> {\n const ast = parseFile(filePath);\n if (!ast) return new Map();\n\n const result = new Map<\n string,\n { importSource: string; library: LibraryName | null }\n >();\n const imports = extractImports(ast);\n\n for (const [name, source] of imports) {\n result.set(name, {\n importSource: source,\n library: detectLibraryFromSource(source),\n });\n }\n\n return result;\n}\n","/**\n * Import Graph Service\n *\n * Provides demand-driven cross-file analysis to detect UI library usage\n * across component trees. Uses in-memory caching for performance.\n */\n\nimport {\n resolveImportPath,\n resolveExport,\n clearResolverCaches,\n} from \"./export-resolver.js\";\nimport {\n parseComponentBody,\n detectLibraryFromSource,\n type LibraryName,\n type ComponentStyleInfo,\n} from \"./component-parser.js\";\n\n/**\n * Information about a component's UI library usage\n */\nexport interface ComponentLibraryInfo {\n /** Direct library (from import source, e.g., \"@mui/material\" -> \"mui\") */\n library: LibraryName | null;\n /** Libraries used internally by this component (for local components) */\n internalLibraries: Set<LibraryName>;\n /** Evidence of which internal components caused the library detection */\n libraryEvidence: Array<{\n componentName: string;\n library: LibraryName;\n }>;\n /** Whether this is a local component (resolved from project files) */\n isLocalComponent: boolean;\n}\n\n/**\n * Cache for analyzed components: \"filePath::componentName\" -> ComponentLibraryInfo\n */\nconst componentLibraryCache = new Map<string, ComponentLibraryInfo>();\n\n/**\n * Get a singleton instance of the import graph service\n */\nexport function getImportGraphService() {\n return {\n getComponentLibrary,\n clearCache,\n };\n}\n\n/**\n * Analyze a component's library usage, including transitive dependencies\n *\n * @param contextFilePath - The file where the component is used (for resolving relative imports)\n * @param componentName - The name of the component (e.g., \"Button\", \"MyCard\")\n * @param importSource - The import source (e.g., \"@mui/material\", \"./components/cards\")\n * @returns Library information including direct and transitive library usage\n */\nexport function getComponentLibrary(\n contextFilePath: string,\n componentName: string,\n importSource: string\n): ComponentLibraryInfo {\n // Check if import source directly indicates a known library\n const directLibrary = detectLibraryFromSource(importSource);\n\n if (directLibrary) {\n // It's a direct import from a known library (e.g., @mui/material)\n return {\n library: directLibrary,\n internalLibraries: new Set(),\n libraryEvidence: [],\n isLocalComponent: false,\n };\n }\n\n // It's a local component - resolve and analyze it\n const resolvedPath = resolveImportPath(importSource, contextFilePath);\n\n if (!resolvedPath) {\n // Could not resolve - might be external or invalid\n return {\n library: null,\n internalLibraries: new Set(),\n libraryEvidence: [],\n isLocalComponent: false,\n };\n }\n\n // Check cache\n const cacheKey = `${resolvedPath}::${componentName}`;\n if (componentLibraryCache.has(cacheKey)) {\n return componentLibraryCache.get(cacheKey)!;\n }\n\n // Resolve re-exports to find the actual component definition\n const resolvedExport = resolveExport(componentName, resolvedPath);\n const actualFilePath = resolvedExport?.filePath ?? resolvedPath;\n const actualComponentName = resolvedExport?.localName ?? componentName;\n\n // Analyze the component body\n const result = analyzeComponentLibraries(\n actualFilePath,\n actualComponentName,\n new Set([cacheKey]) // Track visited to prevent cycles\n );\n\n componentLibraryCache.set(cacheKey, result);\n return result;\n}\n\n/**\n * Recursively analyze a component's library usage\n */\nfunction analyzeComponentLibraries(\n filePath: string,\n componentName: string,\n visited: Set<string>\n): ComponentLibraryInfo {\n const styleInfo = parseComponentBody(filePath, componentName);\n\n if (!styleInfo) {\n return {\n library: null,\n internalLibraries: new Set(),\n libraryEvidence: [],\n isLocalComponent: true,\n };\n }\n\n const internalLibraries = new Set<LibraryName>();\n const libraryEvidence: Array<{\n componentName: string;\n library: LibraryName;\n }> = [];\n\n // Check direct library usage within this component\n if (styleInfo.directLibrary) {\n internalLibraries.add(styleInfo.directLibrary);\n }\n\n // Analyze each used component\n for (const usedComponent of styleInfo.usedComponents) {\n const usedLibrary = detectLibraryFromSource(usedComponent.importSource);\n\n if (usedLibrary) {\n // Direct import from a known library\n internalLibraries.add(usedLibrary);\n libraryEvidence.push({\n componentName: usedComponent.name,\n library: usedLibrary,\n });\n } else {\n // It's a local component - recurse into it\n const resolvedPath = resolveImportPath(\n usedComponent.importSource,\n filePath\n );\n\n if (resolvedPath) {\n const cacheKey = `${resolvedPath}::${usedComponent.name}`;\n\n // Skip if already visited (cycle detection)\n if (!visited.has(cacheKey)) {\n visited.add(cacheKey);\n\n // Check cache first\n let nestedInfo: ComponentLibraryInfo;\n if (componentLibraryCache.has(cacheKey)) {\n nestedInfo = componentLibraryCache.get(cacheKey)!;\n } else {\n // Resolve re-exports\n const resolvedExport = resolveExport(\n usedComponent.name,\n resolvedPath\n );\n const actualFilePath = resolvedExport?.filePath ?? resolvedPath;\n const actualComponentName =\n resolvedExport?.localName ?? usedComponent.name;\n\n nestedInfo = analyzeComponentLibraries(\n actualFilePath,\n actualComponentName,\n visited\n );\n componentLibraryCache.set(cacheKey, nestedInfo);\n }\n\n // Aggregate the nested component's libraries\n if (nestedInfo.library) {\n internalLibraries.add(nestedInfo.library);\n libraryEvidence.push({\n componentName: usedComponent.name,\n library: nestedInfo.library,\n });\n }\n\n for (const lib of nestedInfo.internalLibraries) {\n internalLibraries.add(lib);\n }\n\n // Add evidence from nested components\n for (const evidence of nestedInfo.libraryEvidence) {\n libraryEvidence.push({\n componentName: `${usedComponent.name} → ${evidence.componentName}`,\n library: evidence.library,\n });\n }\n }\n }\n }\n }\n\n return {\n library: styleInfo.directLibrary,\n internalLibraries,\n libraryEvidence,\n isLocalComponent: true,\n };\n}\n\n/**\n * Clear all caches (useful for testing or between ESLint runs)\n */\nexport function clearCache(): void {\n componentLibraryCache.clear();\n clearResolverCaches();\n}\n\n// Re-export types for convenience\nexport type { LibraryName };\n","/**\n * Rule: no-mixed-component-libraries\n *\n * Forbids using non-preferred UI component libraries. Reports errors at\n * the JSX usage site, including transitive usage through local components.\n *\n * Examples:\n * - <MuiButton> from @mui/material -> error at <MuiButton> usage\n * - <MyCard> that internally uses MUI -> error at <MyCard> usage\n */\n\nimport type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule, defineRuleMeta } from \"../../utils/create-rule.js\";\nimport {\n getComponentLibrary,\n type LibraryName,\n} from \"./lib/import-graph.js\";\n\ntype MessageIds = \"nonPreferredLibrary\" | \"transitiveNonPreferred\";\ntype Options = [\n {\n /** The preferred UI library. Components from other libraries will be flagged. */\n preferred: LibraryName;\n /** Additional libraries to detect (defaults to common ones) */\n libraries?: LibraryName[];\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"no-mixed-component-libraries\",\n version: \"1.0.0\",\n name: \"No Mixed Component Libraries\",\n description: \"Forbid mixing component libraries (e.g., shadcn + MUI)\",\n defaultSeverity: \"error\",\n category: \"static\",\n icon: \"🧩\",\n hint: \"Ensures consistent UI library usage\",\n defaultEnabled: true,\n isDirectoryBased: true,\n defaultOptions: [{ preferred: \"shadcn\", libraries: [\"shadcn\", \"mui\"] }],\n optionSchema: {\n fields: [\n {\n key: \"preferred\",\n label: \"Preferred component library\",\n type: \"select\",\n defaultValue: \"shadcn\",\n options: [\n { value: \"shadcn\", label: \"shadcn/ui\" },\n { value: \"mui\", label: \"MUI (Material-UI)\" },\n { value: \"chakra\", label: \"Chakra UI\" },\n { value: \"antd\", label: \"Ant Design\" },\n ],\n description: \"The preferred UI library. Components from other libraries will be flagged.\",\n },\n ],\n },\n docs: `\n## What it does\n\nDetects and reports when components from non-preferred UI libraries are used in your codebase.\nThis includes both direct imports and transitive usage through your own components that wrap\nnon-preferred libraries internally.\n\n## Why it's useful\n\n- **Consistency**: Ensures a uniform look and feel across your application\n- **Bundle size**: Prevents accidentally including multiple UI frameworks\n- **Maintenance**: Reduces the number of styling systems to maintain\n- **Migration support**: Helps identify what needs to change when migrating UI libraries\n\n## Examples\n\n### ❌ Incorrect (with preferred: \"shadcn\")\n\n\\`\\`\\`tsx\n// Direct MUI usage\nimport { Button } from '@mui/material';\n<Button>Click me</Button> // Error: Component <Button> is from mui\n\n// Transitive usage through local component\nimport { MyCard } from './components/MyCard'; // MyCard uses MUI internally\n<MyCard /> // Error: Component <MyCard> internally uses mui components\n\\`\\`\\`\n\n### ✅ Correct\n\n\\`\\`\\`tsx\n// Using the preferred library\nimport { Button } from '@/components/ui/button';\nimport { Card } from '@/components/ui/card';\n\n<Button>Click me</Button>\n<Card>Content</Card>\n\\`\\`\\`\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/no-mixed-component-libraries\": [\"error\", {\n preferred: \"shadcn\", // Your preferred library\n libraries: [\"shadcn\", \"mui\", \"chakra\", \"antd\"] // Libraries to detect\n}]\n\\`\\`\\`\n\n## Supported Libraries\n\n- **shadcn**: shadcn/ui components (imports from \\`@/components/ui/\\`)\n- **mui**: Material-UI (\\`@mui/material\\`, \\`@mui/joy\\`)\n- **chakra**: Chakra UI (\\`@chakra-ui/react\\`)\n- **antd**: Ant Design (\\`antd\\`)\n`,\n\n});\n\n/**\n * Information about a component usage in the file\n */\ninterface ComponentUsage {\n /** The JSX element node (for error reporting) */\n node: TSESTree.JSXOpeningElement;\n /** Component name (e.g., \"Button\", \"MuiCard\") */\n componentName: string;\n /** Import source (e.g., \"@mui/material\", \"./components/cards\") */\n importSource: string;\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"no-mixed-component-libraries\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Forbid using non-preferred UI component libraries. Reports at JSX usage sites, including transitive usage.\",\n },\n messages: {\n nonPreferredLibrary:\n \"Component <{{component}}> is from {{library}}, but {{preferred}} is the preferred library.\",\n transitiveNonPreferred:\n \"Component <{{component}}> internally uses {{libraries}} components ({{internalComponents}}). The preferred library is {{preferred}}.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n preferred: {\n type: \"string\",\n enum: [\"shadcn\", \"mui\", \"chakra\", \"antd\"],\n description: \"The preferred UI library\",\n },\n libraries: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"shadcn\", \"mui\", \"chakra\", \"antd\"],\n },\n description: \"Libraries to detect (defaults to all)\",\n },\n },\n required: [\"preferred\"],\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [\n { preferred: \"shadcn\", libraries: [\"shadcn\", \"mui\", \"chakra\", \"antd\"] },\n ],\n create(context) {\n const options = context.options[0];\n const preferred = options.preferred;\n\n // Track imports: localName -> importSource\n const importMap = new Map<string, string>();\n\n // Track all component usages for analysis at Program:exit\n const componentUsages: ComponentUsage[] = [];\n\n return {\n ImportDeclaration(node) {\n const source = node.source.value as string;\n\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\") {\n importMap.set(spec.local.name, source);\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n importMap.set(spec.local.name, source);\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n importMap.set(spec.local.name, source);\n }\n }\n },\n\n JSXOpeningElement(node) {\n // Get the component name\n let componentName: string | null = null;\n\n if (node.name.type === \"JSXIdentifier\") {\n componentName = node.name.name;\n } else if (node.name.type === \"JSXMemberExpression\") {\n // Handle Namespace.Component (e.g., Modal.Header)\n let current = node.name.object;\n while (current.type === \"JSXMemberExpression\") {\n current = current.object;\n }\n if (current.type === \"JSXIdentifier\") {\n componentName = current.name;\n }\n }\n\n // Skip HTML elements (lowercase) and missing names\n if (!componentName || !/^[A-Z]/.test(componentName)) {\n return;\n }\n\n // Skip components that aren't imported (might be defined in same file)\n const importSource = importMap.get(componentName);\n if (!importSource) {\n return;\n }\n\n componentUsages.push({\n node,\n componentName,\n importSource,\n });\n },\n\n \"Program:exit\"() {\n const filename = context.filename || context.getFilename();\n\n for (const usage of componentUsages) {\n const libraryInfo = getComponentLibrary(\n filename,\n usage.componentName,\n usage.importSource\n );\n\n // Case 1: Direct import from non-preferred library\n if (libraryInfo.library && libraryInfo.library !== preferred) {\n context.report({\n node: usage.node,\n messageId: \"nonPreferredLibrary\",\n data: {\n component: usage.componentName,\n library: libraryInfo.library,\n preferred,\n },\n });\n continue;\n }\n\n // Case 2: Local component that uses non-preferred library internally\n if (\n libraryInfo.isLocalComponent &&\n libraryInfo.internalLibraries.size > 0\n ) {\n const nonPreferredLibs = [...libraryInfo.internalLibraries].filter(\n (lib) => lib !== preferred\n );\n\n if (nonPreferredLibs.length > 0) {\n // Get evidence of which internal components caused the violation\n const internalComponents = libraryInfo.libraryEvidence\n .filter((e) => e.library !== preferred)\n .map((e) => e.componentName)\n .slice(0, 3) // Limit to first 3 for readability\n .join(\", \");\n\n context.report({\n node: usage.node,\n messageId: \"transitiveNonPreferred\",\n data: {\n component: usage.componentName,\n libraries: nonPreferredLibs.join(\", \"),\n internalComponents: internalComponents || \"unknown\",\n preferred,\n },\n });\n }\n }\n }\n },\n };\n },\n});\n"],"mappings":"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","names":["meta"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/create-rule.ts","../../src/rules/no-prop-drilling-depth.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\n/**\n * Schema for prompting user to configure a rule option in the CLI\n */\nexport interface OptionFieldSchema {\n /** Field name in the options object */\n key: string;\n /** Display label for the prompt */\n label: string;\n /** Prompt type */\n type: \"text\" | \"number\" | \"boolean\" | \"select\" | \"multiselect\";\n /** Default value */\n defaultValue: unknown;\n /** Placeholder text (for text/number inputs) */\n placeholder?: string;\n /** Options for select/multiselect */\n options?: Array<{ value: string | number; label: string }>;\n /** Description/hint for the field */\n description?: string;\n}\n\n/**\n * Schema describing how to prompt for rule options during installation\n */\nexport interface RuleOptionSchema {\n /** Fields that can be configured for this rule */\n fields: OptionFieldSchema[];\n}\n\n/**\n * External requirement that a rule needs to function\n */\nexport interface RuleRequirement {\n /** Requirement type for programmatic checks */\n type: \"ollama\" | \"git\" | \"coverage\" | \"semantic-index\" | \"styleguide\";\n /** Human-readable description */\n description: string;\n /** Optional: how to satisfy the requirement */\n setupHint?: string;\n}\n\n/**\n * Rule migration definition for updating rule options between versions\n */\nexport interface RuleMigration {\n /** Source version (semver) */\n from: string;\n /** Target version (semver) */\n to: string;\n /** Human-readable description of what changed */\n description: string;\n /** Function to migrate options from old format to new format */\n migrate: (oldOptions: unknown[]) => unknown[];\n /** Whether this migration contains breaking changes */\n breaking?: boolean;\n}\n\n/**\n * Colocated rule metadata - exported alongside each rule\n *\n * This structure keeps all rule metadata in the same file as the rule implementation,\n * making it easy to maintain and extend as new rules are added.\n */\nexport interface RuleMeta {\n /** Rule identifier (e.g., \"consistent-dark-mode\") - must match filename */\n id: string;\n\n /** Semantic version of the rule (e.g., \"1.0.0\") */\n version: string;\n\n /** Display name for CLI (e.g., \"No Arbitrary Tailwind\") */\n name: string;\n\n /** Short description for CLI selection prompts (one line) */\n description: string;\n\n /** Default severity level */\n defaultSeverity: \"error\" | \"warn\" | \"off\";\n\n /** Category for grouping in CLI */\n category: \"static\" | \"semantic\";\n\n /** Icon for display in CLI/UI (emoji or icon name) */\n icon?: string;\n\n /** Short hint about the rule type/requirements */\n hint?: string;\n\n /** Whether rule is enabled by default during install */\n defaultEnabled?: boolean;\n\n /** External requirements the rule needs */\n requirements?: RuleRequirement[];\n\n /** Instructions to show after installation */\n postInstallInstructions?: string;\n\n /** Framework compatibility */\n frameworks?: (\"next\" | \"vite\" | \"cra\" | \"remix\")[];\n\n /** Whether this rule requires a styleguide file */\n requiresStyleguide?: boolean;\n\n /** Default options for the rule (passed as second element in ESLint config) */\n defaultOptions?: unknown[];\n\n /** Schema for prompting user to configure options during install */\n optionSchema?: RuleOptionSchema;\n\n /**\n * Detailed documentation in markdown format.\n * Should include:\n * - What the rule does\n * - Why it's useful\n * - Examples of incorrect and correct code\n * - Configuration options explained\n */\n docs: string;\n\n /**\n * Internal utility dependencies that this rule requires.\n * When the rule is copied to a target project, these utilities\n * will be transformed to import from \"uilint-eslint\" instead\n * of relative paths.\n *\n * Example: [\"coverage-aggregator\", \"dependency-graph\"]\n */\n internalDependencies?: string[];\n\n /**\n * Whether this rule is directory-based (has lib/ folder with utilities).\n * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.\n * Single-file rules are installed as single .ts files.\n *\n * When true, ESLint config imports will use:\n * ./.uilint/rules/rule-id/index.js\n * When false (default):\n * ./.uilint/rules/rule-id.js\n */\n isDirectoryBased?: boolean;\n\n /**\n * Migrations for updating rule options between versions.\n * Migrations are applied in order to transform options from older versions.\n */\n migrations?: RuleMigration[];\n\n /**\n * Which UI plugin should handle this rule.\n * Defaults based on category:\n * - \"static\" category → \"eslint\" plugin\n * - \"semantic\" category → \"semantic\" plugin\n *\n * Special cases:\n * - \"vision\" for semantic-vision rule\n */\n plugin?: \"eslint\" | \"vision\" | \"semantic\";\n\n /**\n * Custom inspector panel ID to use for this rule's issues.\n * If not specified, uses the plugin's default issue inspector.\n *\n * Examples:\n * - \"vision-issue\" for VisionIssueInspector\n * - \"duplicates\" for DuplicatesInspector\n * - \"semantic-issue\" for SemanticIssueInspector\n */\n customInspector?: string;\n\n /**\n * Custom heatmap color for this rule's issues.\n * CSS color value (hex, rgb, hsl, or named color).\n * If not specified, uses severity-based coloring.\n */\n heatmapColor?: string;\n}\n\n/**\n * Helper to define rule metadata with type safety\n */\nexport function defineRuleMeta(meta: RuleMeta): RuleMeta {\n return meta;\n}\n","/**\n * Rule: no-prop-drilling-depth\n *\n * Warns when a prop is passed through multiple intermediate components\n * without being used, indicating prop drilling that should be refactored\n * to context or state management.\n *\n * Examples:\n * - Bad: Prop passed through 3+ components without use\n * - Good: Prop used directly in receiving component\n * - Good: Using Context or Zustand instead of drilling\n */\n\nimport type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule, defineRuleMeta } from \"../utils/create-rule.js\";\n\ntype MessageIds = \"propDrilling\";\ntype Options = [\n {\n /** Maximum depth before warning (default: 2) */\n maxDepth?: number;\n /** Props to ignore (e.g., className, style, children) */\n ignoredProps?: string[];\n /** Component patterns to skip (regex strings) */\n ignoreComponents?: string[];\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"no-prop-drilling-depth\",\n version: \"1.0.0\",\n name: \"No Prop Drilling Depth\",\n description: \"Warn when props are drilled through too many components\",\n defaultSeverity: \"warn\",\n category: \"static\",\n icon: \"🔗\",\n hint: \"Detects excessive prop passing\",\n defaultEnabled: true,\n defaultOptions: [\n {\n maxDepth: 2,\n ignoredProps: [\"className\", \"style\", \"children\", \"key\", \"ref\", \"id\"],\n ignoreComponents: [],\n },\n ],\n optionSchema: {\n fields: [\n {\n key: \"maxDepth\",\n label: \"Maximum drilling depth\",\n type: \"number\",\n defaultValue: 2,\n description:\n \"Maximum number of components a prop can pass through without use\",\n },\n {\n key: \"ignoredProps\",\n label: \"Ignored props\",\n type: \"text\",\n defaultValue: \"className, style, children, key, ref, id\",\n description: \"Comma-separated prop names to ignore (common pass-through props)\",\n },\n ],\n },\n docs: `\n## What it does\n\nDetects when props are passed through multiple intermediate components without\nbeing used (prop drilling). This is often a sign that you should use React\nContext, Zustand, or another state management solution.\n\n## Why it's useful\n\n- **Maintainability**: Deep prop drilling creates tight coupling\n- **Refactoring**: Changes require updates in many files\n- **Readability**: Hard to trace where props come from\n- **Performance**: Unnecessary re-renders in intermediate components\n\n## Examples\n\n### ❌ Incorrect\n\n\\`\\`\\`tsx\n// Grandparent passes user through Parent to Child\nfunction Grandparent({ user }) {\n return <Parent user={user} />;\n}\n\nfunction Parent({ user }) {\n // Parent doesn't use 'user', just passes it along\n return <Child user={user} />;\n}\n\nfunction Child({ user }) {\n return <div>{user.name}</div>;\n}\n\\`\\`\\`\n\n### ✅ Correct\n\n\\`\\`\\`tsx\n// Use Context instead\nconst UserContext = createContext();\n\nfunction Grandparent({ user }) {\n return (\n <UserContext.Provider value={user}>\n <Parent />\n </UserContext.Provider>\n );\n}\n\nfunction Child() {\n const user = useContext(UserContext);\n return <div>{user.name}</div>;\n}\n\\`\\`\\`\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/no-prop-drilling-depth\": [\"warn\", {\n maxDepth: 2, // Allow passing through 2 components\n ignoredProps: [\"className\", \"style\", \"children\"], // Common pass-through props\n ignoreComponents: [\"^Layout\", \"^Wrapper\"] // Skip wrapper components\n}]\n\\`\\`\\`\n`,\n});\n\n/**\n * Information about a component's prop usage\n */\ninterface ComponentPropInfo {\n /** Props received by the component */\n receivedProps: Set<string>;\n /** Props passed to child components: propName -> childComponentNames[] */\n passedProps: Map<string, string[]>;\n /** Props actually used in the component (not just passed) */\n usedProps: Set<string>;\n /** Child components that receive props from this component */\n childComponents: string[];\n}\n\n/**\n * Cache for analyzed component prop information\n */\nconst componentPropCache = new Map<string, ComponentPropInfo>();\n\n/**\n * Clear the prop analysis cache\n */\nexport function clearPropCache(): void {\n componentPropCache.clear();\n}\n\n/**\n * Check if a name is a React component (PascalCase)\n */\nfunction isComponentName(name: string): boolean {\n return /^[A-Z][a-zA-Z0-9]*$/.test(name);\n}\n\n/**\n * Extract props from a function parameter\n */\nfunction extractPropsFromParam(\n param: TSESTree.Parameter\n): { propNames: Set<string>; isSpread: boolean } {\n const propNames = new Set<string>();\n let isSpread = false;\n\n if (param.type === \"ObjectPattern\") {\n for (const prop of param.properties) {\n if (prop.type === \"RestElement\") {\n isSpread = true;\n } else if (\n prop.type === \"Property\" &&\n prop.key.type === \"Identifier\"\n ) {\n propNames.add(prop.key.name);\n }\n }\n } else if (param.type === \"Identifier\") {\n // Single props parameter - assume all props accessed via props.x\n isSpread = true;\n }\n\n return { propNames, isSpread };\n}\n\n/**\n * Find all JSX elements in a function body and extract prop passing info\n */\nfunction analyzeJSXPropPassing(\n body: TSESTree.Node,\n receivedProps: Set<string>\n): { passedProps: Map<string, string[]>; usedProps: Set<string> } {\n const passedProps = new Map<string, string[]>();\n const usedProps = new Set<string>();\n\n function visit(node: TSESTree.Node): void {\n if (!node || typeof node !== \"object\") return;\n\n // Check JSX elements for prop passing\n if (node.type === \"JSXOpeningElement\") {\n const elementName = getJSXElementName(node.name);\n\n // Only care about component elements (PascalCase)\n if (elementName && isComponentName(elementName)) {\n for (const attr of node.attributes) {\n if (attr.type === \"JSXAttribute\" && attr.name.type === \"JSXIdentifier\") {\n const attrName = attr.name.name;\n const propValue = attr.value;\n\n // Check if the attribute value is a received prop\n if (propValue?.type === \"JSXExpressionContainer\") {\n const expr = propValue.expression;\n if (expr.type === \"Identifier\" && receivedProps.has(expr.name)) {\n // This prop is being passed to a child\n const existing = passedProps.get(expr.name) || [];\n existing.push(elementName);\n passedProps.set(expr.name, existing);\n } else if (\n expr.type === \"MemberExpression\" &&\n expr.object.type === \"Identifier\" &&\n expr.object.name === \"props\" &&\n expr.property.type === \"Identifier\"\n ) {\n // props.x pattern\n const propName = expr.property.name;\n if (receivedProps.has(propName) || receivedProps.size === 0) {\n const existing = passedProps.get(propName) || [];\n existing.push(elementName);\n passedProps.set(propName, existing);\n }\n }\n }\n }\n\n // Check for spread props: {...props} or {...rest}\n if (attr.type === \"JSXSpreadAttribute\") {\n if (attr.argument.type === \"Identifier\") {\n const spreadName = attr.argument.name;\n if (spreadName === \"props\" || receivedProps.has(spreadName)) {\n // All props are being spread\n for (const prop of receivedProps) {\n const existing = passedProps.get(prop) || [];\n existing.push(elementName);\n passedProps.set(prop, existing);\n }\n }\n }\n }\n }\n }\n }\n\n // Check for prop usage (not just passing)\n // e.g., {user.name} or {props.user.name} or just {user}\n if (\n node.type === \"MemberExpression\" &&\n node.object.type === \"Identifier\" &&\n receivedProps.has(node.object.name)\n ) {\n usedProps.add(node.object.name);\n }\n\n if (\n node.type === \"Identifier\" &&\n receivedProps.has(node.name) &&\n node.parent?.type !== \"JSXExpressionContainer\"\n ) {\n // Prop used in expression (but not directly passed to child)\n usedProps.add(node.name);\n }\n\n // Check for props.x.something usage\n if (\n node.type === \"MemberExpression\" &&\n node.object.type === \"MemberExpression\" &&\n node.object.object.type === \"Identifier\" &&\n node.object.object.name === \"props\" &&\n node.object.property.type === \"Identifier\"\n ) {\n usedProps.add(node.object.property.name);\n }\n\n // Recurse into children\n for (const key of Object.keys(node)) {\n if (key === \"parent\" || key === \"loc\" || key === \"range\") continue;\n const child = (node as unknown as Record<string, unknown>)[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\") {\n visit(item as TSESTree.Node);\n }\n }\n } else if (child && typeof child === \"object\") {\n visit(child as TSESTree.Node);\n }\n }\n }\n\n visit(body);\n return { passedProps, usedProps };\n}\n\n/**\n * Get the name of a JSX element\n */\nfunction getJSXElementName(node: TSESTree.JSXTagNameExpression): string | null {\n if (node.type === \"JSXIdentifier\") {\n return node.name;\n }\n if (node.type === \"JSXMemberExpression\") {\n // Get the root object for namespace components\n let current = node.object;\n while (current.type === \"JSXMemberExpression\") {\n current = current.object;\n }\n return current.type === \"JSXIdentifier\" ? current.name : null;\n }\n return null;\n}\n\n/**\n * Track prop drilling within a single file\n */\ninterface PropDrillingInfo {\n propName: string;\n component: string;\n passedTo: string[];\n usedDirectly: boolean;\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"no-prop-drilling-depth\",\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Warn when props are drilled through too many components\",\n },\n messages: {\n propDrilling:\n \"Prop '{{propName}}' is passed through {{depth}} component(s) without being used. Consider using Context or state management. Path: {{path}}\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n maxDepth: {\n type: \"number\",\n minimum: 1,\n description: \"Maximum drilling depth before warning\",\n },\n ignoredProps: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Props to ignore\",\n },\n ignoreComponents: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Component patterns to skip (regex)\",\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [\n {\n maxDepth: 2,\n ignoredProps: [\"className\", \"style\", \"children\", \"key\", \"ref\", \"id\"],\n ignoreComponents: [],\n },\n ],\n create(context) {\n const options = context.options[0] || {};\n const maxDepth = options.maxDepth ?? 2;\n const ignoredProps = new Set(\n options.ignoredProps ?? [\n \"className\",\n \"style\",\n \"children\",\n \"key\",\n \"ref\",\n \"id\",\n ]\n );\n const ignoreComponentPatterns = (options.ignoreComponents ?? []).map(\n (p) => new RegExp(p)\n );\n\n // Track components and their prop flows within the file\n const componentProps = new Map<string, ComponentPropInfo>();\n const imports = new Map<string, string>(); // localName -> importSource\n const componentNodes = new Map<string, TSESTree.Node>(); // componentName -> node\n\n function shouldIgnoreComponent(name: string): boolean {\n return ignoreComponentPatterns.some((pattern) => pattern.test(name));\n }\n\n function shouldIgnoreProp(name: string): boolean {\n return ignoredProps.has(name);\n }\n\n /**\n * Analyze a component function for prop drilling\n */\n function analyzeComponent(\n name: string,\n node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,\n reportNode: TSESTree.Node\n ): void {\n if (shouldIgnoreComponent(name)) return;\n\n const firstParam = node.params[0];\n if (!firstParam) return;\n\n const { propNames, isSpread } = extractPropsFromParam(firstParam);\n\n // If using spread without destructuring, we can't easily track props\n if (isSpread && propNames.size === 0) return;\n\n const body = node.body;\n if (!body) return;\n\n const { passedProps, usedProps } = analyzeJSXPropPassing(body, propNames);\n\n componentProps.set(name, {\n receivedProps: propNames,\n passedProps,\n usedProps,\n childComponents: [...new Set([...passedProps.values()].flat())],\n });\n\n componentNodes.set(name, reportNode);\n }\n\n return {\n // Track imports for cross-file analysis\n ImportDeclaration(node) {\n const source = node.source.value as string;\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\" || spec.type === \"ImportDefaultSpecifier\") {\n imports.set(spec.local.name, source);\n }\n }\n },\n\n // Analyze function declarations\n FunctionDeclaration(node) {\n if (node.id && isComponentName(node.id.name)) {\n analyzeComponent(node.id.name, node, node);\n }\n },\n\n // Analyze arrow functions\n VariableDeclarator(node) {\n if (\n node.id.type === \"Identifier\" &&\n isComponentName(node.id.name) &&\n node.init?.type === \"ArrowFunctionExpression\"\n ) {\n analyzeComponent(node.id.name, node.init, node);\n }\n },\n\n // Analyze at the end of the file\n \"Program:exit\"() {\n // Find drilling chains within the file\n for (const [componentName, info] of componentProps) {\n for (const [propName, children] of info.passedProps) {\n if (shouldIgnoreProp(propName)) continue;\n\n // Check if prop is used directly\n if (info.usedProps.has(propName)) continue;\n\n // Track the drilling chain\n const chain: string[] = [componentName];\n let depth = 0;\n let current = children;\n\n while (current.length > 0 && depth < maxDepth + 1) {\n depth++;\n const nextChildren: string[] = [];\n\n for (const child of current) {\n chain.push(child);\n const childInfo = componentProps.get(child);\n\n if (childInfo) {\n // Check if child uses the prop\n if (childInfo.usedProps.has(propName)) {\n // Prop is used here, drilling stops\n break;\n }\n\n // Check if child passes the prop further\n const childPasses = childInfo.passedProps.get(propName);\n if (childPasses) {\n nextChildren.push(...childPasses);\n }\n }\n }\n\n current = nextChildren;\n }\n\n // Report if depth exceeds threshold\n if (depth > maxDepth) {\n const reportNode = componentNodes.get(componentName);\n if (reportNode) {\n context.report({\n node: reportNode,\n messageId: \"propDrilling\",\n data: {\n propName,\n depth: String(depth),\n path: chain.slice(0, maxDepth + 2).join(\" → \"),\n },\n });\n }\n }\n }\n }\n },\n };\n },\n});\n"],"mappings":"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","names":["meta"]}
1
+ {"version":3,"sources":["../../src/utils/create-rule.ts","../../src/rules/no-prop-drilling-depth.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\n/**\n * Schema for prompting user to configure a rule option in the CLI\n */\nexport interface OptionFieldSchema {\n /** Field name in the options object */\n key: string;\n /** Display label for the prompt */\n label: string;\n /** Prompt type */\n type: \"text\" | \"number\" | \"boolean\" | \"select\" | \"multiselect\";\n /** Default value */\n defaultValue: unknown;\n /** Placeholder text (for text/number inputs) */\n placeholder?: string;\n /** Options for select/multiselect */\n options?: Array<{ value: string | number; label: string }>;\n /** Description/hint for the field */\n description?: string;\n}\n\n/**\n * Schema describing how to prompt for rule options during installation\n */\nexport interface RuleOptionSchema {\n /** Fields that can be configured for this rule */\n fields: OptionFieldSchema[];\n}\n\n/**\n * External requirement that a rule needs to function\n */\nexport interface RuleRequirement {\n /** Requirement type for programmatic checks */\n type: \"ollama\" | \"git\" | \"coverage\" | \"semantic-index\" | \"styleguide\";\n /** Human-readable description */\n description: string;\n /** Optional: how to satisfy the requirement */\n setupHint?: string;\n}\n\n/**\n * Rule migration definition for updating rule options between versions\n */\nexport interface RuleMigration {\n /** Source version (semver) */\n from: string;\n /** Target version (semver) */\n to: string;\n /** Human-readable description of what changed */\n description: string;\n /** Function to migrate options from old format to new format */\n migrate: (oldOptions: unknown[]) => unknown[];\n /** Whether this migration contains breaking changes */\n breaking?: boolean;\n}\n\n/**\n * Colocated rule metadata - exported alongside each rule\n *\n * This structure keeps all rule metadata in the same file as the rule implementation,\n * making it easy to maintain and extend as new rules are added.\n */\nexport interface RuleMeta {\n /** Rule identifier (e.g., \"consistent-dark-mode\") - must match filename */\n id: string;\n\n /** Semantic version of the rule (e.g., \"1.0.0\") */\n version: string;\n\n /** Display name for CLI (e.g., \"No Arbitrary Tailwind\") */\n name: string;\n\n /** Short description for CLI selection prompts (one line) */\n description: string;\n\n /** Default severity level */\n defaultSeverity: \"error\" | \"warn\" | \"off\";\n\n /** Category for grouping in CLI */\n category: \"static\" | \"semantic\";\n\n /** Icon for display in CLI/UI (emoji or icon name) */\n icon?: string;\n\n /** Short hint about the rule type/requirements */\n hint?: string;\n\n /** Whether rule is enabled by default during install */\n defaultEnabled?: boolean;\n\n /** External requirements the rule needs */\n requirements?: RuleRequirement[];\n\n /**\n * NPM packages that must be installed for this rule to work.\n * These will be added to the target project's dependencies during installation.\n *\n * Example: [\"xxhash-wasm\"] for rules using the xxhash library\n */\n npmDependencies?: string[];\n\n /** Instructions to show after installation */\n postInstallInstructions?: string;\n\n /** Framework compatibility */\n frameworks?: (\"next\" | \"vite\" | \"cra\" | \"remix\")[];\n\n /** Whether this rule requires a styleguide file */\n requiresStyleguide?: boolean;\n\n /** Default options for the rule (passed as second element in ESLint config) */\n defaultOptions?: unknown[];\n\n /** Schema for prompting user to configure options during install */\n optionSchema?: RuleOptionSchema;\n\n /**\n * Detailed documentation in markdown format.\n * Should include:\n * - What the rule does\n * - Why it's useful\n * - Examples of incorrect and correct code\n * - Configuration options explained\n */\n docs: string;\n\n /**\n * Internal utility dependencies that this rule requires.\n * When the rule is copied to a target project, these utilities\n * will be transformed to import from \"uilint-eslint\" instead\n * of relative paths.\n *\n * Example: [\"coverage-aggregator\", \"dependency-graph\"]\n */\n internalDependencies?: string[];\n\n /**\n * Whether this rule is directory-based (has lib/ folder with utilities).\n * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.\n * Single-file rules are installed as single .ts files.\n *\n * When true, ESLint config imports will use:\n * ./.uilint/rules/rule-id/index.js\n * When false (default):\n * ./.uilint/rules/rule-id.js\n */\n isDirectoryBased?: boolean;\n\n /**\n * Migrations for updating rule options between versions.\n * Migrations are applied in order to transform options from older versions.\n */\n migrations?: RuleMigration[];\n\n /**\n * Which UI plugin should handle this rule.\n * Defaults based on category:\n * - \"static\" category → \"eslint\" plugin\n * - \"semantic\" category → \"semantic\" plugin\n *\n * Special cases:\n * - \"vision\" for semantic-vision rule\n */\n plugin?: \"eslint\" | \"vision\" | \"semantic\";\n\n /**\n * Custom inspector panel ID to use for this rule's issues.\n * If not specified, uses the plugin's default issue inspector.\n *\n * Examples:\n * - \"vision-issue\" for VisionIssueInspector\n * - \"duplicates\" for DuplicatesInspector\n * - \"semantic-issue\" for SemanticIssueInspector\n */\n customInspector?: string;\n\n /**\n * Custom heatmap color for this rule's issues.\n * CSS color value (hex, rgb, hsl, or named color).\n * If not specified, uses severity-based coloring.\n */\n heatmapColor?: string;\n}\n\n/**\n * Helper to define rule metadata with type safety\n */\nexport function defineRuleMeta(meta: RuleMeta): RuleMeta {\n return meta;\n}\n","/**\n * Rule: no-prop-drilling-depth\n *\n * Warns when a prop is passed through multiple intermediate components\n * without being used, indicating prop drilling that should be refactored\n * to context or state management.\n *\n * Examples:\n * - Bad: Prop passed through 3+ components without use\n * - Good: Prop used directly in receiving component\n * - Good: Using Context or Zustand instead of drilling\n */\n\nimport type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule, defineRuleMeta } from \"../utils/create-rule.js\";\n\ntype MessageIds = \"propDrilling\";\ntype Options = [\n {\n /** Maximum depth before warning (default: 2) */\n maxDepth?: number;\n /** Props to ignore (e.g., className, style, children) */\n ignoredProps?: string[];\n /** Component patterns to skip (regex strings) */\n ignoreComponents?: string[];\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"no-prop-drilling-depth\",\n version: \"1.0.0\",\n name: \"No Prop Drilling Depth\",\n description: \"Warn when props are drilled through too many components\",\n defaultSeverity: \"warn\",\n category: \"static\",\n icon: \"🔗\",\n hint: \"Detects excessive prop passing\",\n defaultEnabled: true,\n defaultOptions: [\n {\n maxDepth: 2,\n ignoredProps: [\"className\", \"style\", \"children\", \"key\", \"ref\", \"id\"],\n ignoreComponents: [],\n },\n ],\n optionSchema: {\n fields: [\n {\n key: \"maxDepth\",\n label: \"Maximum drilling depth\",\n type: \"number\",\n defaultValue: 2,\n description:\n \"Maximum number of components a prop can pass through without use\",\n },\n {\n key: \"ignoredProps\",\n label: \"Ignored props\",\n type: \"text\",\n defaultValue: \"className, style, children, key, ref, id\",\n description: \"Comma-separated prop names to ignore (common pass-through props)\",\n },\n ],\n },\n docs: `\n## What it does\n\nDetects when props are passed through multiple intermediate components without\nbeing used (prop drilling). This is often a sign that you should use React\nContext, Zustand, or another state management solution.\n\n## Why it's useful\n\n- **Maintainability**: Deep prop drilling creates tight coupling\n- **Refactoring**: Changes require updates in many files\n- **Readability**: Hard to trace where props come from\n- **Performance**: Unnecessary re-renders in intermediate components\n\n## Examples\n\n### ❌ Incorrect\n\n\\`\\`\\`tsx\n// Grandparent passes user through Parent to Child\nfunction Grandparent({ user }) {\n return <Parent user={user} />;\n}\n\nfunction Parent({ user }) {\n // Parent doesn't use 'user', just passes it along\n return <Child user={user} />;\n}\n\nfunction Child({ user }) {\n return <div>{user.name}</div>;\n}\n\\`\\`\\`\n\n### ✅ Correct\n\n\\`\\`\\`tsx\n// Use Context instead\nconst UserContext = createContext();\n\nfunction Grandparent({ user }) {\n return (\n <UserContext.Provider value={user}>\n <Parent />\n </UserContext.Provider>\n );\n}\n\nfunction Child() {\n const user = useContext(UserContext);\n return <div>{user.name}</div>;\n}\n\\`\\`\\`\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/no-prop-drilling-depth\": [\"warn\", {\n maxDepth: 2, // Allow passing through 2 components\n ignoredProps: [\"className\", \"style\", \"children\"], // Common pass-through props\n ignoreComponents: [\"^Layout\", \"^Wrapper\"] // Skip wrapper components\n}]\n\\`\\`\\`\n`,\n});\n\n/**\n * Information about a component's prop usage\n */\ninterface ComponentPropInfo {\n /** Props received by the component */\n receivedProps: Set<string>;\n /** Props passed to child components: propName -> childComponentNames[] */\n passedProps: Map<string, string[]>;\n /** Props actually used in the component (not just passed) */\n usedProps: Set<string>;\n /** Child components that receive props from this component */\n childComponents: string[];\n}\n\n/**\n * Cache for analyzed component prop information\n */\nconst componentPropCache = new Map<string, ComponentPropInfo>();\n\n/**\n * Clear the prop analysis cache\n */\nexport function clearPropCache(): void {\n componentPropCache.clear();\n}\n\n/**\n * Check if a name is a React component (PascalCase)\n */\nfunction isComponentName(name: string): boolean {\n return /^[A-Z][a-zA-Z0-9]*$/.test(name);\n}\n\n/**\n * Extract props from a function parameter\n */\nfunction extractPropsFromParam(\n param: TSESTree.Parameter\n): { propNames: Set<string>; isSpread: boolean } {\n const propNames = new Set<string>();\n let isSpread = false;\n\n if (param.type === \"ObjectPattern\") {\n for (const prop of param.properties) {\n if (prop.type === \"RestElement\") {\n isSpread = true;\n } else if (\n prop.type === \"Property\" &&\n prop.key.type === \"Identifier\"\n ) {\n propNames.add(prop.key.name);\n }\n }\n } else if (param.type === \"Identifier\") {\n // Single props parameter - assume all props accessed via props.x\n isSpread = true;\n }\n\n return { propNames, isSpread };\n}\n\n/**\n * Find all JSX elements in a function body and extract prop passing info\n */\nfunction analyzeJSXPropPassing(\n body: TSESTree.Node,\n receivedProps: Set<string>\n): { passedProps: Map<string, string[]>; usedProps: Set<string> } {\n const passedProps = new Map<string, string[]>();\n const usedProps = new Set<string>();\n\n function visit(node: TSESTree.Node): void {\n if (!node || typeof node !== \"object\") return;\n\n // Check JSX elements for prop passing\n if (node.type === \"JSXOpeningElement\") {\n const elementName = getJSXElementName(node.name);\n\n // Only care about component elements (PascalCase)\n if (elementName && isComponentName(elementName)) {\n for (const attr of node.attributes) {\n if (attr.type === \"JSXAttribute\" && attr.name.type === \"JSXIdentifier\") {\n const attrName = attr.name.name;\n const propValue = attr.value;\n\n // Check if the attribute value is a received prop\n if (propValue?.type === \"JSXExpressionContainer\") {\n const expr = propValue.expression;\n if (expr.type === \"Identifier\" && receivedProps.has(expr.name)) {\n // This prop is being passed to a child\n const existing = passedProps.get(expr.name) || [];\n existing.push(elementName);\n passedProps.set(expr.name, existing);\n } else if (\n expr.type === \"MemberExpression\" &&\n expr.object.type === \"Identifier\" &&\n expr.object.name === \"props\" &&\n expr.property.type === \"Identifier\"\n ) {\n // props.x pattern\n const propName = expr.property.name;\n if (receivedProps.has(propName) || receivedProps.size === 0) {\n const existing = passedProps.get(propName) || [];\n existing.push(elementName);\n passedProps.set(propName, existing);\n }\n }\n }\n }\n\n // Check for spread props: {...props} or {...rest}\n if (attr.type === \"JSXSpreadAttribute\") {\n if (attr.argument.type === \"Identifier\") {\n const spreadName = attr.argument.name;\n if (spreadName === \"props\" || receivedProps.has(spreadName)) {\n // All props are being spread\n for (const prop of receivedProps) {\n const existing = passedProps.get(prop) || [];\n existing.push(elementName);\n passedProps.set(prop, existing);\n }\n }\n }\n }\n }\n }\n }\n\n // Check for prop usage (not just passing)\n // e.g., {user.name} or {props.user.name} or just {user}\n if (\n node.type === \"MemberExpression\" &&\n node.object.type === \"Identifier\" &&\n receivedProps.has(node.object.name)\n ) {\n usedProps.add(node.object.name);\n }\n\n if (\n node.type === \"Identifier\" &&\n receivedProps.has(node.name) &&\n node.parent?.type !== \"JSXExpressionContainer\"\n ) {\n // Prop used in expression (but not directly passed to child)\n usedProps.add(node.name);\n }\n\n // Check for props.x.something usage\n if (\n node.type === \"MemberExpression\" &&\n node.object.type === \"MemberExpression\" &&\n node.object.object.type === \"Identifier\" &&\n node.object.object.name === \"props\" &&\n node.object.property.type === \"Identifier\"\n ) {\n usedProps.add(node.object.property.name);\n }\n\n // Recurse into children\n for (const key of Object.keys(node)) {\n if (key === \"parent\" || key === \"loc\" || key === \"range\") continue;\n const child = (node as unknown as Record<string, unknown>)[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\") {\n visit(item as TSESTree.Node);\n }\n }\n } else if (child && typeof child === \"object\") {\n visit(child as TSESTree.Node);\n }\n }\n }\n\n visit(body);\n return { passedProps, usedProps };\n}\n\n/**\n * Get the name of a JSX element\n */\nfunction getJSXElementName(node: TSESTree.JSXTagNameExpression): string | null {\n if (node.type === \"JSXIdentifier\") {\n return node.name;\n }\n if (node.type === \"JSXMemberExpression\") {\n // Get the root object for namespace components\n let current = node.object;\n while (current.type === \"JSXMemberExpression\") {\n current = current.object;\n }\n return current.type === \"JSXIdentifier\" ? current.name : null;\n }\n return null;\n}\n\n/**\n * Track prop drilling within a single file\n */\ninterface PropDrillingInfo {\n propName: string;\n component: string;\n passedTo: string[];\n usedDirectly: boolean;\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"no-prop-drilling-depth\",\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Warn when props are drilled through too many components\",\n },\n messages: {\n propDrilling:\n \"Prop '{{propName}}' is passed through {{depth}} component(s) without being used. Consider using Context or state management. Path: {{path}}\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n maxDepth: {\n type: \"number\",\n minimum: 1,\n description: \"Maximum drilling depth before warning\",\n },\n ignoredProps: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Props to ignore\",\n },\n ignoreComponents: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Component patterns to skip (regex)\",\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [\n {\n maxDepth: 2,\n ignoredProps: [\"className\", \"style\", \"children\", \"key\", \"ref\", \"id\"],\n ignoreComponents: [],\n },\n ],\n create(context) {\n const options = context.options[0] || {};\n const maxDepth = options.maxDepth ?? 2;\n const ignoredProps = new Set(\n options.ignoredProps ?? [\n \"className\",\n \"style\",\n \"children\",\n \"key\",\n \"ref\",\n \"id\",\n ]\n );\n const ignoreComponentPatterns = (options.ignoreComponents ?? []).map(\n (p) => new RegExp(p)\n );\n\n // Track components and their prop flows within the file\n const componentProps = new Map<string, ComponentPropInfo>();\n const imports = new Map<string, string>(); // localName -> importSource\n const componentNodes = new Map<string, TSESTree.Node>(); // componentName -> node\n\n function shouldIgnoreComponent(name: string): boolean {\n return ignoreComponentPatterns.some((pattern) => pattern.test(name));\n }\n\n function shouldIgnoreProp(name: string): boolean {\n return ignoredProps.has(name);\n }\n\n /**\n * Analyze a component function for prop drilling\n */\n function analyzeComponent(\n name: string,\n node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,\n reportNode: TSESTree.Node\n ): void {\n if (shouldIgnoreComponent(name)) return;\n\n const firstParam = node.params[0];\n if (!firstParam) return;\n\n const { propNames, isSpread } = extractPropsFromParam(firstParam);\n\n // If using spread without destructuring, we can't easily track props\n if (isSpread && propNames.size === 0) return;\n\n const body = node.body;\n if (!body) return;\n\n const { passedProps, usedProps } = analyzeJSXPropPassing(body, propNames);\n\n componentProps.set(name, {\n receivedProps: propNames,\n passedProps,\n usedProps,\n childComponents: [...new Set([...passedProps.values()].flat())],\n });\n\n componentNodes.set(name, reportNode);\n }\n\n return {\n // Track imports for cross-file analysis\n ImportDeclaration(node) {\n const source = node.source.value as string;\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\" || spec.type === \"ImportDefaultSpecifier\") {\n imports.set(spec.local.name, source);\n }\n }\n },\n\n // Analyze function declarations\n FunctionDeclaration(node) {\n if (node.id && isComponentName(node.id.name)) {\n analyzeComponent(node.id.name, node, node);\n }\n },\n\n // Analyze arrow functions\n VariableDeclarator(node) {\n if (\n node.id.type === \"Identifier\" &&\n isComponentName(node.id.name) &&\n node.init?.type === \"ArrowFunctionExpression\"\n ) {\n analyzeComponent(node.id.name, node.init, node);\n }\n },\n\n // Analyze at the end of the file\n \"Program:exit\"() {\n // Find drilling chains within the file\n for (const [componentName, info] of componentProps) {\n for (const [propName, children] of info.passedProps) {\n if (shouldIgnoreProp(propName)) continue;\n\n // Check if prop is used directly\n if (info.usedProps.has(propName)) continue;\n\n // Track the drilling chain\n const chain: string[] = [componentName];\n let depth = 0;\n let current = children;\n\n while (current.length > 0 && depth < maxDepth + 1) {\n depth++;\n const nextChildren: string[] = [];\n\n for (const child of current) {\n chain.push(child);\n const childInfo = componentProps.get(child);\n\n if (childInfo) {\n // Check if child uses the prop\n if (childInfo.usedProps.has(propName)) {\n // Prop is used here, drilling stops\n break;\n }\n\n // Check if child passes the prop further\n const childPasses = childInfo.passedProps.get(propName);\n if (childPasses) {\n nextChildren.push(...childPasses);\n }\n }\n }\n\n current = nextChildren;\n }\n\n // Report if depth exceeds threshold\n if (depth > maxDepth) {\n const reportNode = componentNodes.get(componentName);\n if (reportNode) {\n context.report({\n node: reportNode,\n messageId: \"propDrilling\",\n data: {\n propName,\n depth: String(depth),\n path: chain.slice(0, maxDepth + 2).join(\" → \"),\n },\n });\n }\n }\n }\n }\n },\n };\n },\n});\n"],"mappings":"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","names":["meta"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/create-rule.ts","../../src/rules/no-secrets-in-code.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\n/**\n * Schema for prompting user to configure a rule option in the CLI\n */\nexport interface OptionFieldSchema {\n /** Field name in the options object */\n key: string;\n /** Display label for the prompt */\n label: string;\n /** Prompt type */\n type: \"text\" | \"number\" | \"boolean\" | \"select\" | \"multiselect\";\n /** Default value */\n defaultValue: unknown;\n /** Placeholder text (for text/number inputs) */\n placeholder?: string;\n /** Options for select/multiselect */\n options?: Array<{ value: string | number; label: string }>;\n /** Description/hint for the field */\n description?: string;\n}\n\n/**\n * Schema describing how to prompt for rule options during installation\n */\nexport interface RuleOptionSchema {\n /** Fields that can be configured for this rule */\n fields: OptionFieldSchema[];\n}\n\n/**\n * External requirement that a rule needs to function\n */\nexport interface RuleRequirement {\n /** Requirement type for programmatic checks */\n type: \"ollama\" | \"git\" | \"coverage\" | \"semantic-index\" | \"styleguide\";\n /** Human-readable description */\n description: string;\n /** Optional: how to satisfy the requirement */\n setupHint?: string;\n}\n\n/**\n * Rule migration definition for updating rule options between versions\n */\nexport interface RuleMigration {\n /** Source version (semver) */\n from: string;\n /** Target version (semver) */\n to: string;\n /** Human-readable description of what changed */\n description: string;\n /** Function to migrate options from old format to new format */\n migrate: (oldOptions: unknown[]) => unknown[];\n /** Whether this migration contains breaking changes */\n breaking?: boolean;\n}\n\n/**\n * Colocated rule metadata - exported alongside each rule\n *\n * This structure keeps all rule metadata in the same file as the rule implementation,\n * making it easy to maintain and extend as new rules are added.\n */\nexport interface RuleMeta {\n /** Rule identifier (e.g., \"consistent-dark-mode\") - must match filename */\n id: string;\n\n /** Semantic version of the rule (e.g., \"1.0.0\") */\n version: string;\n\n /** Display name for CLI (e.g., \"No Arbitrary Tailwind\") */\n name: string;\n\n /** Short description for CLI selection prompts (one line) */\n description: string;\n\n /** Default severity level */\n defaultSeverity: \"error\" | \"warn\" | \"off\";\n\n /** Category for grouping in CLI */\n category: \"static\" | \"semantic\";\n\n /** Icon for display in CLI/UI (emoji or icon name) */\n icon?: string;\n\n /** Short hint about the rule type/requirements */\n hint?: string;\n\n /** Whether rule is enabled by default during install */\n defaultEnabled?: boolean;\n\n /** External requirements the rule needs */\n requirements?: RuleRequirement[];\n\n /** Instructions to show after installation */\n postInstallInstructions?: string;\n\n /** Framework compatibility */\n frameworks?: (\"next\" | \"vite\" | \"cra\" | \"remix\")[];\n\n /** Whether this rule requires a styleguide file */\n requiresStyleguide?: boolean;\n\n /** Default options for the rule (passed as second element in ESLint config) */\n defaultOptions?: unknown[];\n\n /** Schema for prompting user to configure options during install */\n optionSchema?: RuleOptionSchema;\n\n /**\n * Detailed documentation in markdown format.\n * Should include:\n * - What the rule does\n * - Why it's useful\n * - Examples of incorrect and correct code\n * - Configuration options explained\n */\n docs: string;\n\n /**\n * Internal utility dependencies that this rule requires.\n * When the rule is copied to a target project, these utilities\n * will be transformed to import from \"uilint-eslint\" instead\n * of relative paths.\n *\n * Example: [\"coverage-aggregator\", \"dependency-graph\"]\n */\n internalDependencies?: string[];\n\n /**\n * Whether this rule is directory-based (has lib/ folder with utilities).\n * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.\n * Single-file rules are installed as single .ts files.\n *\n * When true, ESLint config imports will use:\n * ./.uilint/rules/rule-id/index.js\n * When false (default):\n * ./.uilint/rules/rule-id.js\n */\n isDirectoryBased?: boolean;\n\n /**\n * Migrations for updating rule options between versions.\n * Migrations are applied in order to transform options from older versions.\n */\n migrations?: RuleMigration[];\n\n /**\n * Which UI plugin should handle this rule.\n * Defaults based on category:\n * - \"static\" category → \"eslint\" plugin\n * - \"semantic\" category → \"semantic\" plugin\n *\n * Special cases:\n * - \"vision\" for semantic-vision rule\n */\n plugin?: \"eslint\" | \"vision\" | \"semantic\";\n\n /**\n * Custom inspector panel ID to use for this rule's issues.\n * If not specified, uses the plugin's default issue inspector.\n *\n * Examples:\n * - \"vision-issue\" for VisionIssueInspector\n * - \"duplicates\" for DuplicatesInspector\n * - \"semantic-issue\" for SemanticIssueInspector\n */\n customInspector?: string;\n\n /**\n * Custom heatmap color for this rule's issues.\n * CSS color value (hex, rgb, hsl, or named color).\n * If not specified, uses severity-based coloring.\n */\n heatmapColor?: string;\n}\n\n/**\n * Helper to define rule metadata with type safety\n */\nexport function defineRuleMeta(meta: RuleMeta): RuleMeta {\n return meta;\n}\n","/**\n * Rule: no-secrets-in-code\n *\n * Detects hardcoded secrets, API keys, passwords, and tokens in source code.\n * Prevents accidental exposure of sensitive credentials.\n *\n * Examples:\n * - Bad: const apiKey = 'AKIA1234567890ABCDEF'\n * - Bad: const password = 'mySecretPassword123'\n * - Good: const apiKey = process.env.API_KEY\n */\n\nimport { createRule, defineRuleMeta } from \"../utils/create-rule.js\";\nimport type { TSESTree } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"secretDetected\" | \"suspiciousVariable\";\ntype Options = [\n {\n /** Additional regex patterns to detect (as strings) */\n additionalPatterns?: Array<{ name: string; pattern: string }>;\n /** Check variable names for suspicious patterns */\n checkVariableNames?: boolean;\n /** Minimum length for generic secret detection */\n minSecretLength?: number;\n /** Relax rules in test files */\n allowInTestFiles?: boolean;\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"no-secrets-in-code\",\n version: \"1.0.0\",\n name: \"No Secrets in Code\",\n description: \"Detect hardcoded secrets, API keys, and tokens\",\n defaultSeverity: \"error\",\n category: \"static\",\n icon: \"🔐\",\n hint: \"Prevents credential leaks\",\n defaultEnabled: true,\n defaultOptions: [\n {\n checkVariableNames: true,\n minSecretLength: 16,\n allowInTestFiles: false,\n },\n ],\n optionSchema: {\n fields: [\n {\n key: \"checkVariableNames\",\n label: \"Check variable names\",\n type: \"boolean\",\n defaultValue: true,\n description: \"Check for suspicious variable names with high-entropy values\",\n },\n {\n key: \"minSecretLength\",\n label: \"Minimum secret length\",\n type: \"number\",\n defaultValue: 16,\n description: \"Minimum string length for generic secret detection\",\n },\n {\n key: \"allowInTestFiles\",\n label: \"Allow in test files\",\n type: \"boolean\",\n defaultValue: false,\n description: \"Skip detection in test files (*.test.*, *.spec.*)\",\n },\n ],\n },\n docs: `\n## What it does\n\nDetects hardcoded secrets, API keys, passwords, and tokens in source code.\nThese should be stored in environment variables or secure vaults instead.\n\n## Why it's useful\n\n- **Security**: Prevents credential leaks in version control\n- **Compliance**: Helps meet security audit requirements\n- **Best Practice**: Enforces proper secrets management\n\n## Detected Patterns\n\n- AWS Access Keys and Secret Keys\n- GitHub Personal Access Tokens (ghp_*)\n- Stripe API Keys (sk_live_*, sk_test_*)\n- Google API Keys\n- Firebase Keys\n- Slack Tokens\n- npm Tokens\n- JWT Tokens\n- Private Keys (PEM format)\n- Generic API keys, passwords, and secrets in suspicious variables\n\n## Examples\n\n### ❌ Incorrect\n\n\\`\\`\\`tsx\n// Hardcoded AWS credentials\nconst accessKey = 'AKIA1234567890ABCDEF';\nconst secretKey = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';\n\n// Hardcoded passwords\nconst dbPassword = 'supersecretpassword123';\n\n// Hardcoded tokens\nconst token = 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';\n\\`\\`\\`\n\n### ✅ Correct\n\n\\`\\`\\`tsx\n// Use environment variables\nconst accessKey = process.env.AWS_ACCESS_KEY_ID;\nconst secretKey = process.env.AWS_SECRET_ACCESS_KEY;\n\n// Reference from config\nconst dbPassword = config.database.password;\n\n// Use a secrets manager\nconst token = await secretsManager.getSecret('github-token');\n\\`\\`\\`\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/no-secrets-in-code\": [\"error\", {\n checkVariableNames: true, // Check suspicious variable names\n minSecretLength: 16, // Minimum length for generic detection\n allowInTestFiles: false, // Don't skip test files\n additionalPatterns: [ // Add custom patterns\n { name: \"Custom API\", pattern: \"^myapi_[a-z0-9]{32}$\" }\n ]\n}]\n\\`\\`\\`\n`,\n});\n\n/**\n * Known secret patterns with names and regex\n */\nconst SECRET_PATTERNS: Array<{ name: string; pattern: RegExp }> = [\n // AWS\n { name: \"AWS Access Key ID\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/ },\n { name: \"AWS Secret Access Key\", pattern: /\\b[A-Za-z0-9/+=]{40}\\b/ },\n\n // GitHub\n { name: \"GitHub Personal Access Token\", pattern: /\\bghp_[A-Za-z0-9]{36}\\b/ },\n { name: \"GitHub OAuth Token\", pattern: /\\bgho_[A-Za-z0-9]{36}\\b/ },\n { name: \"GitHub App Token\", pattern: /\\bghu_[A-Za-z0-9]{36}\\b/ },\n { name: \"GitHub Refresh Token\", pattern: /\\bghr_[A-Za-z0-9]{36}\\b/ },\n\n // Stripe\n { name: \"Stripe Live Secret Key\", pattern: /\\bsk_live_[A-Za-z0-9]{24,}\\b/ },\n { name: \"Stripe Test Secret Key\", pattern: /\\bsk_test_[A-Za-z0-9]{24,}\\b/ },\n { name: \"Stripe Restricted Key\", pattern: /\\brk_live_[A-Za-z0-9]{24,}\\b/ },\n\n // Google\n { name: \"Google API Key\", pattern: /\\bAIza[A-Za-z0-9_-]{35}\\b/ },\n\n // Slack\n { name: \"Slack Token\", pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,48}\\b/ },\n { name: \"Slack Webhook\", pattern: /\\bhooks\\.slack\\.com\\/services\\/T[A-Za-z0-9]+\\/B[A-Za-z0-9]+\\/[A-Za-z0-9]+\\b/ },\n\n // npm\n { name: \"npm Token\", pattern: /\\bnpm_[A-Za-z0-9]{36}\\b/ },\n\n // SendGrid\n { name: \"SendGrid API Key\", pattern: /\\bSG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}\\b/ },\n\n // Twilio\n { name: \"Twilio API Key\", pattern: /\\bSK[a-z0-9]{32}\\b/ },\n\n // Firebase\n { name: \"Firebase Key\", pattern: /\\bAAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}\\b/ },\n\n // Generic patterns\n { name: \"Private Key\", pattern: /-----BEGIN\\s+(RSA\\s+|EC\\s+|DSA\\s+|OPENSSH\\s+)?PRIVATE\\s+KEY-----/ },\n { name: \"JWT Token\", pattern: /\\beyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/ },\n\n // Anthropic\n { name: \"Anthropic API Key\", pattern: /\\bsk-ant-api[A-Za-z0-9_-]{20,}\\b/ },\n\n // OpenAI\n { name: \"OpenAI API Key\", pattern: /\\bsk-proj-[A-Za-z0-9_-]{20,}\\b/ },\n { name: \"OpenAI API Key (old)\", pattern: /\\bsk-[A-Za-z0-9]{48}\\b/ },\n];\n\n/**\n * Variable name patterns that suggest secrets\n */\nconst SUSPICIOUS_VARIABLE_PATTERNS: RegExp[] = [\n /^api[_-]?key$/i,\n /^secret[_-]?key$/i,\n /^private[_-]?key$/i,\n /^access[_-]?key$/i,\n /^auth[_-]?key$/i,\n /^access[_-]?token$/i,\n /^auth[_-]?token$/i,\n /^api[_-]?token$/i,\n /^bearer[_-]?token$/i,\n /^jwt[_-]?token$/i,\n /^refresh[_-]?token$/i,\n /^password$/i,\n /^passwd$/i,\n /^pwd$/i,\n /^db[_-]?password$/i,\n /^database[_-]?password$/i,\n /^secret$/i,\n /^client[_-]?secret$/i,\n /^app[_-]?secret$/i,\n];\n\n/**\n * Patterns that indicate safe/placeholder values\n */\nconst PLACEHOLDER_PATTERNS: RegExp[] = [\n /^your[_-]?/i,\n /^xxx+$/i,\n /^placeholder/i,\n /^example/i,\n /^test[_-]?/i,\n /^fake[_-]?/i,\n /^dummy/i,\n /^sample/i,\n /<[^>]+>/, // <your-key-here>\n /\\${[^}]+}/, // ${API_KEY}\n /^\\*+$/, // ****\n];\n\n/**\n * Calculate Shannon entropy of a string\n */\nfunction calculateEntropy(str: string): number {\n if (str.length === 0) return 0;\n\n const freq: Record<string, number> = {};\n for (const char of str) {\n freq[char] = (freq[char] || 0) + 1;\n }\n\n let entropy = 0;\n const len = str.length;\n for (const count of Object.values(freq)) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n\n return entropy;\n}\n\n/**\n * Check if a value looks like a placeholder\n */\nfunction isPlaceholder(value: string): boolean {\n return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));\n}\n\n/**\n * Check if a value is likely an environment variable reference\n */\nfunction isEnvReference(value: string): boolean {\n return value.includes(\"process.env\") || value.includes(\"import.meta.env\");\n}\n\n/**\n * Get a preview of the secret (first and last few chars)\n */\nfunction getPreview(value: string, maxLength: number = 20): string {\n if (value.length <= maxLength) {\n return value.substring(0, 8) + \"...\";\n }\n return value.substring(0, 8) + \"...\" + value.substring(value.length - 4);\n}\n\n/**\n * Check if file is a test file\n */\nfunction isTestFile(filename: string): boolean {\n return /\\.(test|spec)\\.[jt]sx?$/.test(filename) ||\n /\\/__tests__\\//.test(filename) ||\n /\\/test\\//.test(filename);\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"no-secrets-in-code\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Detect hardcoded secrets, API keys, and tokens\",\n },\n messages: {\n secretDetected:\n \"Potential {{secretType}} detected: '{{preview}}'. Use environment variables instead of hardcoding secrets.\",\n suspiciousVariable:\n \"Variable '{{variableName}}' appears to contain a secret. Use environment variables instead.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n additionalPatterns: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n pattern: { type: \"string\" },\n },\n required: [\"name\", \"pattern\"],\n },\n description: \"Additional patterns to detect\",\n },\n checkVariableNames: {\n type: \"boolean\",\n description: \"Check variable names for suspicious patterns\",\n },\n minSecretLength: {\n type: \"number\",\n description: \"Minimum length for generic secret detection\",\n },\n allowInTestFiles: {\n type: \"boolean\",\n description: \"Skip detection in test files\",\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [\n {\n checkVariableNames: true,\n minSecretLength: 16,\n allowInTestFiles: false,\n },\n ],\n create(context) {\n const options = context.options[0] || {};\n const checkVariableNames = options.checkVariableNames ?? true;\n const minSecretLength = options.minSecretLength ?? 16;\n const allowInTestFiles = options.allowInTestFiles ?? false;\n const additionalPatterns = options.additionalPatterns ?? [];\n\n const filename = context.filename || context.getFilename?.() || \"\";\n\n // Skip test files if configured\n if (allowInTestFiles && isTestFile(filename)) {\n return {};\n }\n\n // Build full pattern list\n const allPatterns = [...SECRET_PATTERNS];\n for (const custom of additionalPatterns) {\n try {\n allPatterns.push({\n name: custom.name,\n pattern: new RegExp(custom.pattern),\n });\n } catch {\n // Invalid regex, skip\n }\n }\n\n /**\n * Check a string value for secrets\n */\n function checkStringForSecrets(\n value: string,\n node: TSESTree.Node,\n variableName?: string\n ): void {\n // Skip empty strings and short strings\n if (!value || value.length < 8) {\n return;\n }\n\n // Skip placeholders\n if (isPlaceholder(value)) {\n return;\n }\n\n // Check against known patterns\n for (const { name, pattern } of allPatterns) {\n if (pattern.test(value)) {\n context.report({\n node,\n messageId: \"secretDetected\",\n data: {\n secretType: name,\n preview: getPreview(value),\n },\n });\n return;\n }\n }\n\n // Check for suspicious variable names with high-entropy values\n if (checkVariableNames && variableName) {\n const isSuspiciousName = SUSPICIOUS_VARIABLE_PATTERNS.some((pattern) =>\n pattern.test(variableName)\n );\n\n if (isSuspiciousName && value.length >= minSecretLength) {\n const entropy = calculateEntropy(value);\n // High entropy (> 3.5) suggests random/secret data\n if (entropy > 3.5) {\n context.report({\n node,\n messageId: \"suspiciousVariable\",\n data: {\n variableName,\n },\n });\n }\n }\n }\n }\n\n /**\n * Get variable name from declarator\n */\n function getVariableName(node: TSESTree.Node): string | undefined {\n if (node.parent?.type === \"VariableDeclarator\") {\n const declarator = node.parent;\n if (declarator.id.type === \"Identifier\") {\n return declarator.id.name;\n }\n }\n if (node.parent?.type === \"Property\") {\n const prop = node.parent;\n if (prop.key.type === \"Identifier\") {\n return prop.key.name;\n }\n }\n return undefined;\n }\n\n return {\n // Check string literals\n Literal(node) {\n if (typeof node.value === \"string\") {\n const variableName = getVariableName(node);\n checkStringForSecrets(node.value, node, variableName);\n }\n },\n\n // Check template literals\n TemplateLiteral(node) {\n // Only check if no expressions (pure string)\n if (node.expressions.length === 0 && node.quasis.length === 1) {\n const value = node.quasis[0].value.raw;\n const variableName = getVariableName(node);\n checkStringForSecrets(value, node, variableName);\n }\n },\n };\n },\n});\n"],"mappings":"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","names":["meta"]}
1
+ {"version":3,"sources":["../../src/utils/create-rule.ts","../../src/rules/no-secrets-in-code.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\n/**\n * Schema for prompting user to configure a rule option in the CLI\n */\nexport interface OptionFieldSchema {\n /** Field name in the options object */\n key: string;\n /** Display label for the prompt */\n label: string;\n /** Prompt type */\n type: \"text\" | \"number\" | \"boolean\" | \"select\" | \"multiselect\";\n /** Default value */\n defaultValue: unknown;\n /** Placeholder text (for text/number inputs) */\n placeholder?: string;\n /** Options for select/multiselect */\n options?: Array<{ value: string | number; label: string }>;\n /** Description/hint for the field */\n description?: string;\n}\n\n/**\n * Schema describing how to prompt for rule options during installation\n */\nexport interface RuleOptionSchema {\n /** Fields that can be configured for this rule */\n fields: OptionFieldSchema[];\n}\n\n/**\n * External requirement that a rule needs to function\n */\nexport interface RuleRequirement {\n /** Requirement type for programmatic checks */\n type: \"ollama\" | \"git\" | \"coverage\" | \"semantic-index\" | \"styleguide\";\n /** Human-readable description */\n description: string;\n /** Optional: how to satisfy the requirement */\n setupHint?: string;\n}\n\n/**\n * Rule migration definition for updating rule options between versions\n */\nexport interface RuleMigration {\n /** Source version (semver) */\n from: string;\n /** Target version (semver) */\n to: string;\n /** Human-readable description of what changed */\n description: string;\n /** Function to migrate options from old format to new format */\n migrate: (oldOptions: unknown[]) => unknown[];\n /** Whether this migration contains breaking changes */\n breaking?: boolean;\n}\n\n/**\n * Colocated rule metadata - exported alongside each rule\n *\n * This structure keeps all rule metadata in the same file as the rule implementation,\n * making it easy to maintain and extend as new rules are added.\n */\nexport interface RuleMeta {\n /** Rule identifier (e.g., \"consistent-dark-mode\") - must match filename */\n id: string;\n\n /** Semantic version of the rule (e.g., \"1.0.0\") */\n version: string;\n\n /** Display name for CLI (e.g., \"No Arbitrary Tailwind\") */\n name: string;\n\n /** Short description for CLI selection prompts (one line) */\n description: string;\n\n /** Default severity level */\n defaultSeverity: \"error\" | \"warn\" | \"off\";\n\n /** Category for grouping in CLI */\n category: \"static\" | \"semantic\";\n\n /** Icon for display in CLI/UI (emoji or icon name) */\n icon?: string;\n\n /** Short hint about the rule type/requirements */\n hint?: string;\n\n /** Whether rule is enabled by default during install */\n defaultEnabled?: boolean;\n\n /** External requirements the rule needs */\n requirements?: RuleRequirement[];\n\n /**\n * NPM packages that must be installed for this rule to work.\n * These will be added to the target project's dependencies during installation.\n *\n * Example: [\"xxhash-wasm\"] for rules using the xxhash library\n */\n npmDependencies?: string[];\n\n /** Instructions to show after installation */\n postInstallInstructions?: string;\n\n /** Framework compatibility */\n frameworks?: (\"next\" | \"vite\" | \"cra\" | \"remix\")[];\n\n /** Whether this rule requires a styleguide file */\n requiresStyleguide?: boolean;\n\n /** Default options for the rule (passed as second element in ESLint config) */\n defaultOptions?: unknown[];\n\n /** Schema for prompting user to configure options during install */\n optionSchema?: RuleOptionSchema;\n\n /**\n * Detailed documentation in markdown format.\n * Should include:\n * - What the rule does\n * - Why it's useful\n * - Examples of incorrect and correct code\n * - Configuration options explained\n */\n docs: string;\n\n /**\n * Internal utility dependencies that this rule requires.\n * When the rule is copied to a target project, these utilities\n * will be transformed to import from \"uilint-eslint\" instead\n * of relative paths.\n *\n * Example: [\"coverage-aggregator\", \"dependency-graph\"]\n */\n internalDependencies?: string[];\n\n /**\n * Whether this rule is directory-based (has lib/ folder with utilities).\n * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.\n * Single-file rules are installed as single .ts files.\n *\n * When true, ESLint config imports will use:\n * ./.uilint/rules/rule-id/index.js\n * When false (default):\n * ./.uilint/rules/rule-id.js\n */\n isDirectoryBased?: boolean;\n\n /**\n * Migrations for updating rule options between versions.\n * Migrations are applied in order to transform options from older versions.\n */\n migrations?: RuleMigration[];\n\n /**\n * Which UI plugin should handle this rule.\n * Defaults based on category:\n * - \"static\" category → \"eslint\" plugin\n * - \"semantic\" category → \"semantic\" plugin\n *\n * Special cases:\n * - \"vision\" for semantic-vision rule\n */\n plugin?: \"eslint\" | \"vision\" | \"semantic\";\n\n /**\n * Custom inspector panel ID to use for this rule's issues.\n * If not specified, uses the plugin's default issue inspector.\n *\n * Examples:\n * - \"vision-issue\" for VisionIssueInspector\n * - \"duplicates\" for DuplicatesInspector\n * - \"semantic-issue\" for SemanticIssueInspector\n */\n customInspector?: string;\n\n /**\n * Custom heatmap color for this rule's issues.\n * CSS color value (hex, rgb, hsl, or named color).\n * If not specified, uses severity-based coloring.\n */\n heatmapColor?: string;\n}\n\n/**\n * Helper to define rule metadata with type safety\n */\nexport function defineRuleMeta(meta: RuleMeta): RuleMeta {\n return meta;\n}\n","/**\n * Rule: no-secrets-in-code\n *\n * Detects hardcoded secrets, API keys, passwords, and tokens in source code.\n * Prevents accidental exposure of sensitive credentials.\n *\n * Examples:\n * - Bad: const apiKey = 'AKIA1234567890ABCDEF'\n * - Bad: const password = 'mySecretPassword123'\n * - Good: const apiKey = process.env.API_KEY\n */\n\nimport { createRule, defineRuleMeta } from \"../utils/create-rule.js\";\nimport type { TSESTree } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"secretDetected\" | \"suspiciousVariable\";\ntype Options = [\n {\n /** Additional regex patterns to detect (as strings) */\n additionalPatterns?: Array<{ name: string; pattern: string }>;\n /** Check variable names for suspicious patterns */\n checkVariableNames?: boolean;\n /** Minimum length for generic secret detection */\n minSecretLength?: number;\n /** Relax rules in test files */\n allowInTestFiles?: boolean;\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"no-secrets-in-code\",\n version: \"1.0.0\",\n name: \"No Secrets in Code\",\n description: \"Detect hardcoded secrets, API keys, and tokens\",\n defaultSeverity: \"error\",\n category: \"static\",\n icon: \"🔐\",\n hint: \"Prevents credential leaks\",\n defaultEnabled: true,\n defaultOptions: [\n {\n checkVariableNames: true,\n minSecretLength: 16,\n allowInTestFiles: false,\n },\n ],\n optionSchema: {\n fields: [\n {\n key: \"checkVariableNames\",\n label: \"Check variable names\",\n type: \"boolean\",\n defaultValue: true,\n description: \"Check for suspicious variable names with high-entropy values\",\n },\n {\n key: \"minSecretLength\",\n label: \"Minimum secret length\",\n type: \"number\",\n defaultValue: 16,\n description: \"Minimum string length for generic secret detection\",\n },\n {\n key: \"allowInTestFiles\",\n label: \"Allow in test files\",\n type: \"boolean\",\n defaultValue: false,\n description: \"Skip detection in test files (*.test.*, *.spec.*)\",\n },\n ],\n },\n docs: `\n## What it does\n\nDetects hardcoded secrets, API keys, passwords, and tokens in source code.\nThese should be stored in environment variables or secure vaults instead.\n\n## Why it's useful\n\n- **Security**: Prevents credential leaks in version control\n- **Compliance**: Helps meet security audit requirements\n- **Best Practice**: Enforces proper secrets management\n\n## Detected Patterns\n\n- AWS Access Keys and Secret Keys\n- GitHub Personal Access Tokens (ghp_*)\n- Stripe API Keys (sk_live_*, sk_test_*)\n- Google API Keys\n- Firebase Keys\n- Slack Tokens\n- npm Tokens\n- JWT Tokens\n- Private Keys (PEM format)\n- Generic API keys, passwords, and secrets in suspicious variables\n\n## Examples\n\n### ❌ Incorrect\n\n\\`\\`\\`tsx\n// Hardcoded AWS credentials\nconst accessKey = 'AKIA1234567890ABCDEF';\nconst secretKey = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';\n\n// Hardcoded passwords\nconst dbPassword = 'supersecretpassword123';\n\n// Hardcoded tokens\nconst token = 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';\n\\`\\`\\`\n\n### ✅ Correct\n\n\\`\\`\\`tsx\n// Use environment variables\nconst accessKey = process.env.AWS_ACCESS_KEY_ID;\nconst secretKey = process.env.AWS_SECRET_ACCESS_KEY;\n\n// Reference from config\nconst dbPassword = config.database.password;\n\n// Use a secrets manager\nconst token = await secretsManager.getSecret('github-token');\n\\`\\`\\`\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/no-secrets-in-code\": [\"error\", {\n checkVariableNames: true, // Check suspicious variable names\n minSecretLength: 16, // Minimum length for generic detection\n allowInTestFiles: false, // Don't skip test files\n additionalPatterns: [ // Add custom patterns\n { name: \"Custom API\", pattern: \"^myapi_[a-z0-9]{32}$\" }\n ]\n}]\n\\`\\`\\`\n`,\n});\n\n/**\n * Known secret patterns with names and regex\n */\nconst SECRET_PATTERNS: Array<{ name: string; pattern: RegExp }> = [\n // AWS\n { name: \"AWS Access Key ID\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/ },\n { name: \"AWS Secret Access Key\", pattern: /\\b[A-Za-z0-9/+=]{40}\\b/ },\n\n // GitHub\n { name: \"GitHub Personal Access Token\", pattern: /\\bghp_[A-Za-z0-9]{36}\\b/ },\n { name: \"GitHub OAuth Token\", pattern: /\\bgho_[A-Za-z0-9]{36}\\b/ },\n { name: \"GitHub App Token\", pattern: /\\bghu_[A-Za-z0-9]{36}\\b/ },\n { name: \"GitHub Refresh Token\", pattern: /\\bghr_[A-Za-z0-9]{36}\\b/ },\n\n // Stripe\n { name: \"Stripe Live Secret Key\", pattern: /\\bsk_live_[A-Za-z0-9]{24,}\\b/ },\n { name: \"Stripe Test Secret Key\", pattern: /\\bsk_test_[A-Za-z0-9]{24,}\\b/ },\n { name: \"Stripe Restricted Key\", pattern: /\\brk_live_[A-Za-z0-9]{24,}\\b/ },\n\n // Google\n { name: \"Google API Key\", pattern: /\\bAIza[A-Za-z0-9_-]{35}\\b/ },\n\n // Slack\n { name: \"Slack Token\", pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,48}\\b/ },\n { name: \"Slack Webhook\", pattern: /\\bhooks\\.slack\\.com\\/services\\/T[A-Za-z0-9]+\\/B[A-Za-z0-9]+\\/[A-Za-z0-9]+\\b/ },\n\n // npm\n { name: \"npm Token\", pattern: /\\bnpm_[A-Za-z0-9]{36}\\b/ },\n\n // SendGrid\n { name: \"SendGrid API Key\", pattern: /\\bSG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}\\b/ },\n\n // Twilio\n { name: \"Twilio API Key\", pattern: /\\bSK[a-z0-9]{32}\\b/ },\n\n // Firebase\n { name: \"Firebase Key\", pattern: /\\bAAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}\\b/ },\n\n // Generic patterns\n { name: \"Private Key\", pattern: /-----BEGIN\\s+(RSA\\s+|EC\\s+|DSA\\s+|OPENSSH\\s+)?PRIVATE\\s+KEY-----/ },\n { name: \"JWT Token\", pattern: /\\beyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/ },\n\n // Anthropic\n { name: \"Anthropic API Key\", pattern: /\\bsk-ant-api[A-Za-z0-9_-]{20,}\\b/ },\n\n // OpenAI\n { name: \"OpenAI API Key\", pattern: /\\bsk-proj-[A-Za-z0-9_-]{20,}\\b/ },\n { name: \"OpenAI API Key (old)\", pattern: /\\bsk-[A-Za-z0-9]{48}\\b/ },\n];\n\n/**\n * Variable name patterns that suggest secrets\n */\nconst SUSPICIOUS_VARIABLE_PATTERNS: RegExp[] = [\n /^api[_-]?key$/i,\n /^secret[_-]?key$/i,\n /^private[_-]?key$/i,\n /^access[_-]?key$/i,\n /^auth[_-]?key$/i,\n /^access[_-]?token$/i,\n /^auth[_-]?token$/i,\n /^api[_-]?token$/i,\n /^bearer[_-]?token$/i,\n /^jwt[_-]?token$/i,\n /^refresh[_-]?token$/i,\n /^password$/i,\n /^passwd$/i,\n /^pwd$/i,\n /^db[_-]?password$/i,\n /^database[_-]?password$/i,\n /^secret$/i,\n /^client[_-]?secret$/i,\n /^app[_-]?secret$/i,\n];\n\n/**\n * Patterns that indicate safe/placeholder values\n */\nconst PLACEHOLDER_PATTERNS: RegExp[] = [\n /^your[_-]?/i,\n /^xxx+$/i,\n /^placeholder/i,\n /^example/i,\n /^test[_-]?/i,\n /^fake[_-]?/i,\n /^dummy/i,\n /^sample/i,\n /<[^>]+>/, // <your-key-here>\n /\\${[^}]+}/, // ${API_KEY}\n /^\\*+$/, // ****\n];\n\n/**\n * Calculate Shannon entropy of a string\n */\nfunction calculateEntropy(str: string): number {\n if (str.length === 0) return 0;\n\n const freq: Record<string, number> = {};\n for (const char of str) {\n freq[char] = (freq[char] || 0) + 1;\n }\n\n let entropy = 0;\n const len = str.length;\n for (const count of Object.values(freq)) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n\n return entropy;\n}\n\n/**\n * Check if a value looks like a placeholder\n */\nfunction isPlaceholder(value: string): boolean {\n return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));\n}\n\n/**\n * Check if a value is likely an environment variable reference\n */\nfunction isEnvReference(value: string): boolean {\n return value.includes(\"process.env\") || value.includes(\"import.meta.env\");\n}\n\n/**\n * Get a preview of the secret (first and last few chars)\n */\nfunction getPreview(value: string, maxLength: number = 20): string {\n if (value.length <= maxLength) {\n return value.substring(0, 8) + \"...\";\n }\n return value.substring(0, 8) + \"...\" + value.substring(value.length - 4);\n}\n\n/**\n * Check if file is a test file\n */\nfunction isTestFile(filename: string): boolean {\n return /\\.(test|spec)\\.[jt]sx?$/.test(filename) ||\n /\\/__tests__\\//.test(filename) ||\n /\\/test\\//.test(filename);\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"no-secrets-in-code\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Detect hardcoded secrets, API keys, and tokens\",\n },\n messages: {\n secretDetected:\n \"Potential {{secretType}} detected: '{{preview}}'. Use environment variables instead of hardcoding secrets.\",\n suspiciousVariable:\n \"Variable '{{variableName}}' appears to contain a secret. Use environment variables instead.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n additionalPatterns: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n pattern: { type: \"string\" },\n },\n required: [\"name\", \"pattern\"],\n },\n description: \"Additional patterns to detect\",\n },\n checkVariableNames: {\n type: \"boolean\",\n description: \"Check variable names for suspicious patterns\",\n },\n minSecretLength: {\n type: \"number\",\n description: \"Minimum length for generic secret detection\",\n },\n allowInTestFiles: {\n type: \"boolean\",\n description: \"Skip detection in test files\",\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [\n {\n checkVariableNames: true,\n minSecretLength: 16,\n allowInTestFiles: false,\n },\n ],\n create(context) {\n const options = context.options[0] || {};\n const checkVariableNames = options.checkVariableNames ?? true;\n const minSecretLength = options.minSecretLength ?? 16;\n const allowInTestFiles = options.allowInTestFiles ?? false;\n const additionalPatterns = options.additionalPatterns ?? [];\n\n const filename = context.filename || context.getFilename?.() || \"\";\n\n // Skip test files if configured\n if (allowInTestFiles && isTestFile(filename)) {\n return {};\n }\n\n // Build full pattern list\n const allPatterns = [...SECRET_PATTERNS];\n for (const custom of additionalPatterns) {\n try {\n allPatterns.push({\n name: custom.name,\n pattern: new RegExp(custom.pattern),\n });\n } catch {\n // Invalid regex, skip\n }\n }\n\n /**\n * Check a string value for secrets\n */\n function checkStringForSecrets(\n value: string,\n node: TSESTree.Node,\n variableName?: string\n ): void {\n // Skip empty strings and short strings\n if (!value || value.length < 8) {\n return;\n }\n\n // Skip placeholders\n if (isPlaceholder(value)) {\n return;\n }\n\n // Check against known patterns\n for (const { name, pattern } of allPatterns) {\n if (pattern.test(value)) {\n context.report({\n node,\n messageId: \"secretDetected\",\n data: {\n secretType: name,\n preview: getPreview(value),\n },\n });\n return;\n }\n }\n\n // Check for suspicious variable names with high-entropy values\n if (checkVariableNames && variableName) {\n const isSuspiciousName = SUSPICIOUS_VARIABLE_PATTERNS.some((pattern) =>\n pattern.test(variableName)\n );\n\n if (isSuspiciousName && value.length >= minSecretLength) {\n const entropy = calculateEntropy(value);\n // High entropy (> 3.5) suggests random/secret data\n if (entropy > 3.5) {\n context.report({\n node,\n messageId: \"suspiciousVariable\",\n data: {\n variableName,\n },\n });\n }\n }\n }\n }\n\n /**\n * Get variable name from declarator\n */\n function getVariableName(node: TSESTree.Node): string | undefined {\n if (node.parent?.type === \"VariableDeclarator\") {\n const declarator = node.parent;\n if (declarator.id.type === \"Identifier\") {\n return declarator.id.name;\n }\n }\n if (node.parent?.type === \"Property\") {\n const prop = node.parent;\n if (prop.key.type === \"Identifier\") {\n return prop.key.name;\n }\n }\n return undefined;\n }\n\n return {\n // Check string literals\n Literal(node) {\n if (typeof node.value === \"string\") {\n const variableName = getVariableName(node);\n checkStringForSecrets(node.value, node, variableName);\n }\n },\n\n // Check template literals\n TemplateLiteral(node) {\n // Only check if no expressions (pure string)\n if (node.expressions.length === 0 && node.quasis.length === 1) {\n const value = node.quasis[0].value.raw;\n const variableName = getVariableName(node);\n checkStringForSecrets(value, node, variableName);\n }\n },\n };\n },\n});\n"],"mappings":"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","names":["meta"]}