@taiga-ui/eslint-plugin-experience-next 0.372.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -1135,7 +1135,7 @@ function projectJsonExist(filename) {
1135
1135
  }
1136
1136
 
1137
1137
  const allPackageJSONs = globSync('**/package.json', {
1138
- ignore: ['node_modules/**', 'dist/**'],
1138
+ ignore: ['**/node_modules/**', '**/dist/**'],
1139
1139
  }).filter((path) => !readJSON(path).private);
1140
1140
  const packageNames = allPackageJSONs.map((path) => readJSON(path).name).filter(Boolean);
1141
1141
  const packageSourceGlobs = allPackageJSONs.map((p) => p.replaceAll(/\\+/g, '/').replace('package.json', '**/*.ts'));
@@ -1932,61 +1932,63 @@ function getClass(node) {
1932
1932
  const MESSAGE_ID$1 = 'prefer-deep-imports';
1933
1933
  const ERROR_MESSAGE = 'Import via root entry point is prohibited when nested entry points exist';
1934
1934
  const createRule$3 = ESLintUtils.RuleCreator(() => ERROR_MESSAGE);
1935
- const tsconfigCache = new Map();
1936
- const moduleResolutionCache = new Map();
1937
- const exportCheckCache = new Map();
1938
- const nearestNgCache = new Map();
1939
- const tsFileCache = new Map();
1940
1935
  var preferDeepImports = createRule$3({
1941
1936
  create(context, [options]) {
1942
- const allowedPackages = normalizeFilter(options.importFilter);
1943
- const strict = options.strict ?? false;
1937
+ const allowedPackages = normalizeImportFilter(options.importFilter);
1938
+ const isStrictMode = options.strict ?? false;
1939
+ const parserServices = ESLintUtils.getParserServices(context);
1940
+ const program = parserServices.program;
1941
+ const typeChecker = program.getTypeChecker();
1944
1942
  return {
1945
1943
  ImportDeclaration(node) {
1946
- const importPath = node.source.value;
1947
- if (typeof importPath !== 'string') {
1944
+ const rawImportPath = node.source.value;
1945
+ if (typeof rawImportPath !== 'string') {
1948
1946
  return;
1949
1947
  }
1950
- const shortName = extractPackageName(importPath);
1951
- const allowed = allowedPackages.some((pkg) => {
1952
- if (shortName && pkg instanceof RegExp) {
1953
- return pkg.test(shortName);
1954
- }
1955
- return pkg === shortName;
1956
- });
1957
- if (!allowed) {
1948
+ const rootPackageName = getRootPackageName(rawImportPath);
1949
+ if (!rootPackageName) {
1950
+ return;
1951
+ }
1952
+ if (!allowedPackages.includes(rootPackageName)) {
1958
1953
  return;
1959
1954
  }
1960
- if (!strict && isNestedImport(importPath)) {
1955
+ if (!isStrictMode &&
1956
+ isAlreadyNestedImport(rawImportPath, rootPackageName)) {
1961
1957
  return;
1962
1958
  }
1963
- const symbols = extractImportedSymbols(node);
1964
- if (symbols.length === 0) {
1959
+ const importedSymbols = extractNamedImportedSymbols(node);
1960
+ if (importedSymbols.length === 0) {
1965
1961
  return;
1966
1962
  }
1967
- const filename = context.filename;
1968
- const rootEntry = resolveRootEntryPoint(importPath, filename);
1969
- if (!rootEntry) {
1970
- return context.report({
1963
+ const currentFileName = context.getFilename();
1964
+ const rootEntryDirectory = resolveRootEntryDirectory(rawImportPath, currentFileName, program);
1965
+ if (!rootEntryDirectory) {
1966
+ context.report({
1971
1967
  messageId: MESSAGE_ID$1,
1972
1968
  node,
1973
1969
  });
1970
+ return;
1974
1971
  }
1975
- const nested = findNestedEntryPoints(rootEntry);
1976
- if (nested.length === 0) {
1977
- return context.report({
1972
+ const nestedEntryPointRelativePaths = findNestedEntryPointRelativePaths(rootEntryDirectory);
1973
+ if (nestedEntryPointRelativePaths.length === 0) {
1974
+ context.report({
1978
1975
  messageId: MESSAGE_ID$1,
1979
1976
  node,
1980
1977
  });
1978
+ return;
1979
+ }
1980
+ const candidateEntryPointPaths = selectCandidateEntryPointsForMode(nestedEntryPointRelativePaths, isStrictMode);
1981
+ if (candidateEntryPointPaths.length === 0) {
1982
+ return;
1981
1983
  }
1982
- const symbolMap = mapSymbolsToEntryPoints(rootEntry, nested, symbols, strict);
1983
- if (symbolMap.size === 0) {
1984
+ const symbolToEntryPoint = mapSymbolsToEntryPointsUsingTypeChecker(importedSymbols, candidateEntryPointPaths, rootEntryDirectory, program, typeChecker);
1985
+ if (symbolToEntryPoint.size === 0) {
1984
1986
  return;
1985
1987
  }
1986
- const newImports = buildImports(node, importPath, symbolMap);
1988
+ const newImportBlock = buildRewrittenImports(node, rawImportPath, symbolToEntryPoint);
1987
1989
  context.report({
1988
1990
  fix(fixer) {
1989
- return fixer.replaceTextRange(node.range, newImports);
1991
+ return fixer.replaceTextRange(node.range, newImportBlock);
1990
1992
  },
1991
1993
  messageId: MESSAGE_ID$1,
1992
1994
  node,
@@ -2018,244 +2020,264 @@ var preferDeepImports = createRule$3({
2018
2020
  },
2019
2021
  name: 'prefer-deep-imports',
2020
2022
  });
2021
- function extractPackageName(importPath) {
2022
- if (!importPath.startsWith('@')) {
2023
- return null;
2023
+ /**
2024
+ * Normalize "importFilter" option to a flat array of package names.
2025
+ * The rule expects concrete package names, not regular expression strings.
2026
+ */
2027
+ function normalizeImportFilter(importFilter) {
2028
+ return Array.isArray(importFilter) ? importFilter : [importFilter];
2029
+ }
2030
+ /**
2031
+ * Extract the package root name from an import specifier.
2032
+ *
2033
+ * Examples:
2034
+ * "@taiga-ui/core" → "@taiga-ui/core"
2035
+ * "@taiga-ui/core/components" → "@taiga-ui/core"
2036
+ * "some-lib" → "some-lib"
2037
+ * "some-lib/utils" → "some-lib"
2038
+ */
2039
+ function getRootPackageName(importPath) {
2040
+ if (importPath.startsWith('@')) {
2041
+ const segments = importPath.split('/');
2042
+ if (segments.length < 2) {
2043
+ return null;
2044
+ }
2045
+ return `${segments[0]}/${segments[1]}`;
2024
2046
  }
2025
- const p = importPath.split('/');
2026
- return p.length >= 2 ? `${p[0]}/${p[1]}` : null;
2047
+ const parts = importPath.split('/');
2048
+ return parts[0] ?? null;
2027
2049
  }
2028
- function isNestedImport(importPath) {
2029
- return importPath.split('/').length > 2;
2050
+ /**
2051
+ * Check whether the current import path is already nested below the root package.
2052
+ *
2053
+ * Example:
2054
+ * root = "@taiga-ui/core"
2055
+ * "@taiga-ui/core" → false (root import)
2056
+ * "@taiga-ui/core/components" → true
2057
+ * "@taiga-ui/core/components/x" → true
2058
+ */
2059
+ function isAlreadyNestedImport(importPath, rootPackageName) {
2060
+ if (!importPath.startsWith(rootPackageName)) {
2061
+ return false;
2062
+ }
2063
+ const importSegments = importPath.split('/');
2064
+ const rootSegments = rootPackageName.split('/');
2065
+ return importSegments.length > rootSegments.length;
2030
2066
  }
2031
- function extractImportedSymbols(node) {
2067
+ /**
2068
+ * Extract only named imported symbols:
2069
+ *
2070
+ * Examples:
2071
+ * import {A, B as C} from 'x'; → ['A', 'B']
2072
+ *
2073
+ * Namespace imports and default imports are ignored for this rule.
2074
+ */
2075
+ function extractNamedImportedSymbols(node) {
2032
2076
  return node.specifiers
2033
- .filter((s) => s.type === AST_NODE_TYPES$1.ImportSpecifier)
2034
- .map((s) => s.imported.type === AST_NODE_TYPES$1.Identifier
2035
- ? s.imported.name
2036
- : s.imported.value);
2077
+ .filter((specifier) => specifier.type === AST_NODE_TYPES$1.ImportSpecifier)
2078
+ .map((specifier) => specifier.imported.type === AST_NODE_TYPES$1.Identifier
2079
+ ? specifier.imported.name
2080
+ : specifier.imported.value);
2037
2081
  }
2038
- function resolveRootEntryPoint(importPath, fromFile) {
2039
- const key = `${importPath}|${fromFile}`;
2040
- if (moduleResolutionCache.has(key)) {
2041
- return moduleResolutionCache.get(key);
2042
- }
2043
- const tsconfig = findNearestTsconfig(fromFile);
2044
- if (!tsconfig) {
2045
- moduleResolutionCache.set(key, null);
2082
+ /**
2083
+ * Resolve the physical directory of the module being imported.
2084
+ * We rely on the same module resolution that TypeScript uses for the program.
2085
+ */
2086
+ function resolveRootEntryDirectory(importPath, fromFile, program) {
2087
+ const compilerOptions = program.getCompilerOptions();
2088
+ const resolution = ts.resolveModuleName(importPath, fromFile, compilerOptions, ts.sys).resolvedModule;
2089
+ if (!resolution) {
2046
2090
  return null;
2047
2091
  }
2048
- let parsed = tsconfigCache.get(tsconfig);
2049
- if (!parsed) {
2050
- const json = ts.readConfigFile(tsconfig, ts.sys.readFile).config;
2051
- parsed = ts.parseJsonConfigFileContent(json, ts.sys, path.dirname(tsconfig));
2052
- tsconfigCache.set(tsconfig, parsed);
2053
- }
2054
- const resolved = ts.resolveModuleName(importPath, fromFile, parsed.options, ts.sys).resolvedModule;
2055
- const value = resolved ? path.dirname(resolved.resolvedFileName) : null;
2056
- moduleResolutionCache.set(key, value);
2057
- return value;
2092
+ return path.dirname(resolution.resolvedFileName);
2058
2093
  }
2059
- function findNearestTsconfig(start) {
2060
- let dir = path.dirname(start);
2061
- const limit = process.cwd();
2062
- while (dir.startsWith(limit)) {
2063
- const candidate = path.join(dir, 'tsconfig.json');
2064
- if (fs.existsSync(candidate)) {
2065
- return candidate;
2094
+ /**
2095
+ * Find all nested entry points relative to the given root directory.
2096
+ *
2097
+ * A directory is considered a nested entry point if it contains either:
2098
+ * - "ng-package.json" (Angular library entry)
2099
+ * - "collection.json" (Angular schematics collection)
2100
+ *
2101
+ * Returned paths are relative to "rootEntryDirectory".
2102
+ *
2103
+ * Example:
2104
+ * rootEntryDirectory = ".../core/src"
2105
+ * found:
2106
+ * "utils/ng-package.json" → "utils"
2107
+ * "utils/dom/ng-package.json" → "utils/dom"
2108
+ * "schematics/collection.json" → "schematics"
2109
+ */
2110
+ function findNestedEntryPointRelativePaths(rootEntryDirectory) {
2111
+ const ngPackageJsonFiles = globSync('**/ng-package.json', {
2112
+ absolute: false,
2113
+ cwd: rootEntryDirectory,
2114
+ });
2115
+ const collectionJsonFiles = globSync('**/collection.json', {
2116
+ absolute: false,
2117
+ cwd: rootEntryDirectory,
2118
+ });
2119
+ const directories = [];
2120
+ for (const file of ngPackageJsonFiles) {
2121
+ const normalized = file.replaceAll('\\', '/').replace(/\/ng-package\.json$/, '');
2122
+ if (normalized && normalized !== '.') {
2123
+ directories.push(normalized);
2066
2124
  }
2067
- const parent = path.dirname(dir);
2068
- if (parent === dir) {
2069
- break;
2125
+ }
2126
+ for (const file of collectionJsonFiles) {
2127
+ const normalized = file.replaceAll('\\', '/').replace(/\/collection\.json$/, '');
2128
+ if (normalized && normalized !== '.') {
2129
+ directories.push(normalized);
2070
2130
  }
2071
- dir = parent;
2072
2131
  }
2073
- return null;
2132
+ return directories;
2074
2133
  }
2075
- function findNestedEntryPoints(root) {
2076
- const cached = tsFileCache.get(`${root}|ng`);
2077
- if (cached) {
2078
- return cached;
2134
+ /**
2135
+ * For strict = false:
2136
+ * Only first-level nested directories are candidates.
2137
+ *
2138
+ * For strict = true:
2139
+ * All nested directories are candidates, sorted from deepest to shallowest
2140
+ * so that the deepest match wins.
2141
+ */
2142
+ function selectCandidateEntryPointsForMode(allNestedRelativePaths, strict) {
2143
+ if (!strict) {
2144
+ return allNestedRelativePaths.filter((relativePath) => !relativePath.includes('/'));
2079
2145
  }
2080
- const found = globSync('**/ng-package.json', { absolute: false, cwd: root })
2081
- .map((p) => p.replaceAll('\\', '/'))
2082
- .map((p) => p.replace('/ng-package.json', ''))
2083
- .filter((dir) => dir !== '.');
2084
- tsFileCache.set(`${root}|ng`, found);
2085
- return found;
2146
+ return [...allNestedRelativePaths].sort((a, b) => {
2147
+ const depthA = a.split('/').filter(Boolean).length;
2148
+ const depthB = b.split('/').filter(Boolean).length;
2149
+ return depthB - depthA;
2150
+ });
2086
2151
  }
2087
- function mapSymbolsToEntryPoints(root, nested, symbols, strict) {
2088
- const result = new Map();
2089
- const remaining = new Set(symbols);
2090
- for (const np of nested) {
2091
- if (remaining.size === 0) {
2092
- break;
2152
+ /**
2153
+ * Build a map from exported symbol name to nested entry point relative path.
2154
+ *
2155
+ * Implementation strategy:
2156
+ * 1. For each candidate nested entry point:
2157
+ * - Determine its entry file (using ng-package.json or collection.json).
2158
+ * - Ask TypeScript for the module symbol and its exports.
2159
+ * 2. For each imported symbol:
2160
+ * - Find the first entry point whose export table contains that symbol.
2161
+ * - strict = true: candidates were sorted deepest-first.
2162
+ * - strict = false: candidates contain only first-level nested entry points.
2163
+ */
2164
+ function mapSymbolsToEntryPointsUsingTypeChecker(importedSymbols, candidateEntryPoints, rootEntryDirectory, program, typeChecker) {
2165
+ const symbolToEntryPoint = new Map();
2166
+ const exportTableByEntryPoint = new Map();
2167
+ for (const relativeEntryDir of candidateEntryPoints) {
2168
+ const entryFile = getEntryFileForNestedEntryPoint(rootEntryDirectory, relativeEntryDir);
2169
+ if (!entryFile) {
2170
+ continue;
2093
2171
  }
2094
- const full = path.join(root, np);
2095
- let files = tsFileCache.get(full);
2096
- if (!files) {
2097
- files = globSync('**/*.ts', {
2098
- absolute: true,
2099
- cwd: full,
2100
- ignore: ['**/*.spec.ts', '**/*.cy.ts'],
2101
- });
2102
- tsFileCache.set(full, files);
2172
+ const sourceFile = program.getSourceFile(entryFile);
2173
+ if (!sourceFile) {
2174
+ continue;
2103
2175
  }
2104
- for (const file of files) {
2105
- const content = safeReadFile(file);
2106
- if (!content) {
2176
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
2177
+ if (!moduleSymbol) {
2178
+ continue;
2179
+ }
2180
+ const exports$1 = typeChecker.getExportsOfModule(moduleSymbol);
2181
+ const exportedNames = new Set(exports$1.map((symbol) => symbol.getName()));
2182
+ exportTableByEntryPoint.set(relativeEntryDir, exportedNames);
2183
+ }
2184
+ for (const importedSymbol of importedSymbols) {
2185
+ for (const relativeEntryDir of candidateEntryPoints) {
2186
+ const exportedNames = exportTableByEntryPoint.get(relativeEntryDir);
2187
+ if (!exportedNames) {
2107
2188
  continue;
2108
2189
  }
2109
- for (const symbol of Array.from(remaining)) {
2110
- const has = cachedContainsDirectExport(file, content, symbol) ||
2111
- cachedContainsReExport(file, content, symbol);
2112
- if (!has) {
2113
- continue;
2114
- }
2115
- const nearest = cachedNearestNg(file, root);
2116
- if (!nearest) {
2117
- continue;
2118
- }
2119
- let suffix = path.relative(root, nearest).replaceAll('\\', '/');
2120
- if (!strict && suffix.includes('/')) {
2121
- suffix = suffix.split('/')[0];
2122
- }
2123
- result.set(symbol, suffix);
2124
- remaining.delete(symbol);
2190
+ if (!exportedNames.has(importedSymbol)) {
2191
+ continue;
2125
2192
  }
2193
+ symbolToEntryPoint.set(importedSymbol, relativeEntryDir);
2194
+ break;
2126
2195
  }
2127
2196
  }
2128
- return result;
2129
- }
2130
- function cachedContainsDirectExport(file, content, symbol) {
2131
- let cache = exportCheckCache.get(file);
2132
- if (!cache) {
2133
- cache = new Map();
2134
- exportCheckCache.set(file, cache);
2135
- }
2136
- const key = `direct:${symbol}`;
2137
- if (cache.has(key)) {
2138
- return cache.get(key);
2139
- }
2140
- const res = containsDirectExport(content, symbol);
2141
- cache.set(key, res);
2142
- return res;
2143
- }
2144
- function cachedContainsReExport(file, content, symbol) {
2145
- let cache = exportCheckCache.get(file);
2146
- if (!cache) {
2147
- cache = new Map();
2148
- exportCheckCache.set(file, cache);
2149
- }
2150
- const key = `re:${symbol}`;
2151
- if (cache.has(key)) {
2152
- return cache.get(key);
2153
- }
2154
- const res = containsReExport(file, content, symbol);
2155
- cache.set(key, res);
2156
- return res;
2157
- }
2158
- function containsDirectExport(content, symbol) {
2159
- const re = new RegExp(String.raw `(?:export\s+(?:function|class|const|let|var)\s+${symbol}\b)|` +
2160
- String.raw `(?:export\s*\{[^}]*\b${symbol}\b[^}]*\})`);
2161
- return re.test(content);
2197
+ return symbolToEntryPoint;
2162
2198
  }
2163
- function containsReExport(file, content, symbol) {
2164
- const star = /export\s*\*\s*from\s*['"](.+)['"]/g;
2165
- let m;
2166
- while ((m = star.exec(content))) {
2167
- const resolved = resolveReExport(file, m[1]);
2168
- if (!resolved) {
2169
- continue;
2170
- }
2171
- const nested = safeReadFile(resolved);
2172
- if (nested && containsDirectExport(nested, symbol)) {
2173
- return true;
2174
- }
2175
- }
2176
- const named = new RegExp(String.raw `export\s*\{[^}]*\b${symbol}\b[^}]*\}\s*from\s*['"](.+)['"]`);
2177
- const nm = named.exec(content);
2178
- if (nm) {
2179
- const resolved = resolveReExport(file, nm[1]);
2180
- if (!resolved) {
2181
- return false;
2199
+ /**
2200
+ * Determine the physical entry file for a nested entry point.
2201
+ *
2202
+ * Priority:
2203
+ * 1. If "ng-package.json" exists:
2204
+ * - use lib.entryFile if present
2205
+ * - otherwise fall back to "index.ts"
2206
+ * 2. Else if "collection.json" exists:
2207
+ * - treat this directory as a schematic collection package
2208
+ * - entry file is "index.ts" if it exists
2209
+ * 3. Otherwise: no entry file can be determined → return null
2210
+ */
2211
+ function getEntryFileForNestedEntryPoint(rootEntryDirectory, relativeEntryDirectory) {
2212
+ const absoluteDirectory = path.join(rootEntryDirectory, relativeEntryDirectory);
2213
+ const ngPackageJsonPath = path.join(absoluteDirectory, 'ng-package.json');
2214
+ const collectionJsonPath = path.join(absoluteDirectory, 'collection.json');
2215
+ if (fs.existsSync(ngPackageJsonPath)) {
2216
+ try {
2217
+ const raw = fs.readFileSync(ngPackageJsonPath, 'utf8');
2218
+ const json = JSON.parse(raw);
2219
+ const entryRelative = json.lib?.entryFile ?? 'index.ts';
2220
+ return path.resolve(absoluteDirectory, entryRelative);
2182
2221
  }
2183
- const nested = safeReadFile(resolved);
2184
- if (nested && containsDirectExport(nested, symbol)) {
2185
- return true;
2222
+ catch {
2223
+ const fallback = path.resolve(absoluteDirectory, 'index.ts');
2224
+ return fs.existsSync(fallback) ? fallback : null;
2186
2225
  }
2187
2226
  }
2188
- return false;
2189
- }
2190
- function resolveReExport(file, rel) {
2191
- const full = path.resolve(path.dirname(file), rel);
2192
- return fs.existsSync(full) ? full : null;
2193
- }
2194
- function cachedNearestNg(file, root) {
2195
- const key = `${file}|${root}`;
2196
- if (nearestNgCache.has(key)) {
2197
- return nearestNgCache.get(key);
2227
+ if (fs.existsSync(collectionJsonPath)) {
2228
+ const entryFile = path.resolve(absoluteDirectory, 'index.ts');
2229
+ return fs.existsSync(entryFile) ? entryFile : null;
2198
2230
  }
2199
- let dir = path.dirname(file);
2200
- while (dir.startsWith(root)) {
2201
- const candidate = path.join(dir, 'ng-package.json');
2202
- if (fs.existsSync(candidate)) {
2203
- nearestNgCache.set(key, dir);
2204
- return dir;
2205
- }
2206
- const parent = path.dirname(dir);
2207
- if (parent === dir) {
2208
- break;
2209
- }
2210
- dir = parent;
2211
- }
2212
- nearestNgCache.set(key, null);
2213
2231
  return null;
2214
2232
  }
2215
- function buildImports(node, baseImport, symbolMap) {
2216
- const isTypeOnly = node.importKind === 'type';
2217
- const groups = new Map();
2218
- for (const [symbol, suffix] of symbolMap.entries()) {
2219
- const target = suffix ? `${baseImport}/${suffix}` : baseImport;
2220
- if (!groups.has(target)) {
2221
- groups.set(target, []);
2233
+ /**
2234
+ * Build the final text block with rewritten import declarations.
2235
+ *
2236
+ * Example:
2237
+ * original:
2238
+ * import {A, B as C, D} from '@taiga-ui/core';
2239
+ *
2240
+ * symbolMap (strict = true):
2241
+ * A -> "components/button"
2242
+ * B -> "components/button"
2243
+ * D -> "components/other"
2244
+ *
2245
+ * result:
2246
+ * import {A, B as C} from '@taiga-ui/core/components/button';
2247
+ * import {D} from '@taiga-ui/core/components/other';
2248
+ */
2249
+ function buildRewrittenImports(node, baseImportPath, symbolToEntryPoint) {
2250
+ const isTypeOnlyImport = node.importKind === 'type';
2251
+ const groupedByTarget = new Map();
2252
+ for (const [symbolName, relativeEntryPath] of symbolToEntryPoint.entries()) {
2253
+ const targetSpecifier = `${baseImportPath}/${relativeEntryPath}`;
2254
+ if (!groupedByTarget.has(targetSpecifier)) {
2255
+ groupedByTarget.set(targetSpecifier, []);
2222
2256
  }
2223
- groups.get(target).push(symbol);
2257
+ groupedByTarget.get(targetSpecifier).push(symbolName);
2224
2258
  }
2225
- const result = [];
2226
- for (const [target, symbols] of groups.entries()) {
2227
- const importParts = symbols.map((symbol) => {
2228
- const sp = node.specifiers.find((s) => s.type === AST_NODE_TYPES$1.ImportSpecifier &&
2229
- (s.imported.type === AST_NODE_TYPES$1.Identifier
2230
- ? s.imported.name === symbol
2231
- : s.imported.value === symbol));
2232
- if (!sp) {
2233
- return symbol;
2259
+ const importStatements = [];
2260
+ for (const [targetSpecifier, symbols] of groupedByTarget.entries()) {
2261
+ const parts = symbols.map((symbolName) => {
2262
+ const matchingSpecifier = node.specifiers.find((specifier) => specifier.type === AST_NODE_TYPES$1.ImportSpecifier &&
2263
+ (specifier.imported.type === AST_NODE_TYPES$1.Identifier
2264
+ ? specifier.imported.name === symbolName
2265
+ : specifier.imported.value === symbolName));
2266
+ if (!matchingSpecifier) {
2267
+ return symbolName;
2234
2268
  }
2235
- const local = sp.local.name;
2236
- return symbol === local ? symbol : `${symbol} as ${local}`;
2269
+ const importedName = matchingSpecifier.imported.type === AST_NODE_TYPES$1.Identifier
2270
+ ? matchingSpecifier.imported.name
2271
+ : matchingSpecifier.imported.value;
2272
+ const localName = matchingSpecifier.local.name;
2273
+ return importedName === localName
2274
+ ? importedName
2275
+ : `${importedName} as ${localName}`;
2237
2276
  });
2238
- result.push(`import ${isTypeOnly ? 'type ' : ''}{${importParts.join(', ')}} from '${target}';`);
2239
- }
2240
- return result.join('\n');
2241
- }
2242
- function safeReadFile(file) {
2243
- try {
2244
- return fs.readFileSync(file, 'utf8');
2277
+ const statement = `import ${isTypeOnlyImport ? 'type ' : ''}{${parts.join(', ')}} from '${targetSpecifier}';`;
2278
+ importStatements.push(statement);
2245
2279
  }
2246
- catch {
2247
- return null;
2248
- }
2249
- }
2250
- function normalizeFilter(filter) {
2251
- const arr = Array.isArray(filter) ? filter : [filter];
2252
- return arr.map((item) => {
2253
- if (typeof item === 'string' && item.startsWith('/') && item.endsWith('/')) {
2254
- const body = item.slice(1, -1);
2255
- return new RegExp(body);
2256
- }
2257
- return item;
2258
- });
2280
+ return importStatements.join('\n');
2259
2281
  }
2260
2282
 
2261
2283
  function getImportedName(spec) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.372.0",
3
+ "version": "0.373.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,7 +1,20 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
2
  type RuleOptions = [
3
3
  {
4
+ /**
5
+ * List of package names for which this rule should be applied.
6
+ * Usually something like: ['@taiga-ui/core', '@taiga-ui/cdk', ...]
7
+ */
4
8
  importFilter: string[] | string;
9
+ /**
10
+ * strict = false:
11
+ * - Only rewrite imports from the root package path
12
+ * - Target only the first-level nested entry point (e.g. "@pkg/components")
13
+ *
14
+ * strict = true:
15
+ * - May rewrite imports from nested paths as well
16
+ * - Target the deepest nested entry point that actually exports the symbol
17
+ */
5
18
  strict?: boolean;
6
19
  }
7
20
  ];