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