UNPKG

@ospm/eslint-plugin-react-signals-hooks

Version:

ESLint plugin for React Signals hooks - enforces best practices, performance optimizations, and integration patterns for @preact/signals-react usage in React projects

72 lines 3.62 kB
import { AST_NODE_TYPES } from '@typescript-eslint/types'; import { buildNamedImport, getPreferredQuote, getPreferredSemicolon } from './import-format.js'; /** * Ensure that a given named import exists for `moduleName`. * Returns a list of fixes to add/merge the named import in the most idiomatic way. * * Behavior: * - If an import from module exists with a namespace specifier, inserts a separate named import line. * - If it has named specifiers, appends to that list. * - If it only has a default specifier, converts to `default, { named }` form. * - If none exists, inserts a new import near the first import, respecting quote and semicolon style. */ export function ensureNamedImportFixes(context, fixer, moduleName, importName) { const fixes = []; const { sourceCode } = context; const importDeclarations = sourceCode.ast.body.filter((n) => n.type === AST_NODE_TYPES.ImportDeclaration); const existing = importDeclarations.find((d) => d.source.value === moduleName); // If already imported as named, do nothing if (existing) { const hasNamedAlready = existing.specifiers.some((s) => s.type === AST_NODE_TYPES.ImportSpecifier && s.imported.type === AST_NODE_TYPES.Identifier && s.imported.name === importName); if (hasNamedAlready) { return fixes; } const hasNamespace = existing.specifiers.some((s) => s.type === AST_NODE_TYPES.ImportNamespaceSpecifier); if (hasNamespace) { // Safer to add a separate named import line const quote = getPreferredQuote(sourceCode); const semi = getPreferredSemicolon(sourceCode); const text = buildNamedImport(moduleName, [importName], quote, semi); fixes.push(fixer.insertTextAfter(existing, `\n${text}`)); return fixes; } // Try to merge with existing specifiers const lastNamed = [...existing.specifiers] .reverse() .find((s) => s.type === AST_NODE_TYPES.ImportSpecifier); const defaultSpec = existing.specifiers.find((s) => s.type === AST_NODE_TYPES.ImportDefaultSpecifier); if (lastNamed) { fixes.push(fixer.insertTextAfter(lastNamed, `, ${importName}`)); return fixes; } if (defaultSpec) { // Replace entire import to include named list const quote = getPreferredQuote(sourceCode); const semi = getPreferredSemicolon(sourceCode); const newText = `import ${defaultSpec.local.name}, { ${importName} } from ${quote}${moduleName}${quote}${semi ? ';' : ''}`; fixes.push(fixer.replaceText(existing, newText)); return fixes; } // No default, no named, no namespace -> empty? Insert a fresh named import before it const quote = getPreferredQuote(sourceCode); const semi = getPreferredSemicolon(sourceCode); const text = buildNamedImport(moduleName, [importName], quote, semi); fixes.push(fixer.insertTextBefore(existing, text)); return fixes; } // No import from module found -> insert near first import or at top const firstImport = importDeclarations[0]; const quote = getPreferredQuote(sourceCode); const semi = getPreferredSemicolon(sourceCode); const text = buildNamedImport(moduleName, [importName], quote, semi); if (firstImport) { fixes.push(fixer.insertTextBefore(firstImport, text)); } else { fixes.push(fixer.insertTextAfterRange([0, 0], text)); } return fixes; } //# sourceMappingURL=imports.js.map