@taiga-ui/eslint-plugin-experience-next 0.496.0 → 0.498.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.
Files changed (2) hide show
  1. package/index.esm.js +222 -208
  2. package/package.json +3 -5
package/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import fs, { readFileSync } from 'node:fs';
1
+ import fs, { globSync, readFileSync } from 'node:fs';
2
2
  import markdown from '@eslint/markdown';
3
3
  import rxjs from '@smarttools/eslint-plugin-rxjs';
4
4
  import stylistic from '@stylistic/eslint-plugin';
@@ -24,7 +24,6 @@ import unusedImports from 'eslint-plugin-unused-imports';
24
24
  import globals from 'globals';
25
25
  import tseslint from 'typescript-eslint';
26
26
  import { createRequire } from 'node:module';
27
- import { globSync } from 'glob';
28
27
  import require$$0$1 from 'eslint';
29
28
  import require$$1 from 'eslint/use-at-your-own-risk';
30
29
  import path from 'node:path';
@@ -1480,10 +1479,10 @@ var recommended = defineConfig([
1480
1479
  ]);
1481
1480
 
1482
1481
  const allPackageJSONs = globSync('**/package.json', {
1483
- ignore: ['**/node_modules/**', '**/dist/**'],
1482
+ exclude: ['**/node_modules/**', '**/dist/**'],
1484
1483
  }).filter((path) => !readJSON(path).private);
1485
1484
  function pattern(type) {
1486
- return allPackageJSONs.map((p) => p.replaceAll(/\\+/g, '/').replace('package.json', type));
1485
+ return allPackageJSONs.map((p) => p.replaceAll(/\\+/g, '/').replace(/package\.json$/, type));
1487
1486
  }
1488
1487
  var taigaSpecific = defineConfig([
1489
1488
  {
@@ -1495,7 +1494,7 @@ var taigaSpecific = defineConfig([
1495
1494
  'error',
1496
1495
  {
1497
1496
  importFilter: allPackageJSONs
1498
- .map((path) => readJSON(path).name)
1497
+ .map((packageJsonPath) => readJSON(packageJsonPath).name)
1499
1498
  .filter(Boolean),
1500
1499
  strict: !!process.env.CI,
1501
1500
  },
@@ -252224,7 +252223,7 @@ function isImportsArrayProperty(property) {
252224
252223
  return isProperty && hasIdentifierKey && isArray(property.value);
252225
252224
  }
252226
252225
 
252227
- function getImportedName(spec) {
252226
+ function getImportedName$1(spec) {
252228
252227
  if (spec.imported.type === dist$3.AST_NODE_TYPES.Identifier) {
252229
252228
  return spec.imported.name;
252230
252229
  }
@@ -252298,7 +252297,7 @@ const rule$8 = createRule({
252298
252297
  const specifierNames = importDeclaration.specifiers
252299
252298
  .filter((clause) => clause.type === dist$3.AST_NODE_TYPES.ImportSpecifier &&
252300
252299
  clause.importKind !== 'type')
252301
- .map((specifier) => getImportedName(specifier));
252300
+ .map((specifier) => getImportedName$1(specifier));
252302
252301
  const nextNames = new Set(specifierNames);
252303
252302
  nextNames.add(short);
252304
252303
  if (shouldDeleteImport) {
@@ -252308,7 +252307,7 @@ const rule$8 = createRule({
252308
252307
  .filter((clause) => clause.type === dist$3.AST_NODE_TYPES.ImportSpecifier &&
252309
252308
  (clause.importKind === 'type' ||
252310
252309
  importDeclaration.importKind === 'type'))
252311
- .map((specifier) => `type ${getImportedName(specifier)}`);
252310
+ .map((specifier) => `type ${getImportedName$1(specifier)}`);
252312
252311
  const allNames = [...typeImports, ...[...nextNames].sort()];
252313
252312
  const newImport = `import { ${allNames.join(', ')} } from '${module}';`;
252314
252313
  const alreadyHasShort = imports.some((id) => id.name === short);
@@ -252339,7 +252338,7 @@ const rule$8 = createRule({
252339
252338
  if (spec.type !== dist$3.AST_NODE_TYPES.ImportSpecifier) {
252340
252339
  continue;
252341
252340
  }
252342
- const importedClass = getImportedName(spec);
252341
+ const importedClass = getImportedName$1(spec);
252343
252342
  const matchesPattern = /^Tui[A-Z].*(?:Component|Directive)$/.test(importedClass) ||
252344
252343
  exceptions.some((exception) => exception.from === importedClass);
252345
252344
  if (matchesPattern) {
@@ -253079,13 +253078,31 @@ const rule$2 = createRule({
253079
253078
 
253080
253079
  const MESSAGE_ID = 'prefer-deep-imports';
253081
253080
  const ERROR_MESSAGE = 'Import via root entry point is prohibited when nested entry points exist';
253081
+ const sharedStateByProgram = new WeakMap();
253082
253082
  const rule$1 = createRule({
253083
253083
  create(context, [options]) {
253084
- const allowedPackages = normalizeImportFilter(options.importFilter);
253084
+ const allowedPackages = new Set(normalizeImportFilter(options.importFilter));
253085
+ if (allowedPackages.size === 0) {
253086
+ return {};
253087
+ }
253085
253088
  const isStrictMode = options.strict ?? false;
253086
- const parserServices = dist$3.ESLintUtils.getParserServices(context);
253087
- const program = parserServices.program;
253088
- const typeChecker = program.getTypeChecker();
253089
+ let state = null;
253090
+ function getState() {
253091
+ if (state) {
253092
+ return state;
253093
+ }
253094
+ const parserServices = dist$3.ESLintUtils.getParserServices(context);
253095
+ const program = parserServices.program;
253096
+ state = {
253097
+ allowedPackages,
253098
+ isStrictMode,
253099
+ program,
253100
+ shared: getSharedState(program),
253101
+ sourceCode: context.sourceCode,
253102
+ typeChecker: program.getTypeChecker(),
253103
+ };
253104
+ return state;
253105
+ }
253089
253106
  return {
253090
253107
  ImportDeclaration(node) {
253091
253108
  const rawImportPath = node.source.value;
@@ -253094,7 +253111,7 @@ const rule$1 = createRule({
253094
253111
  }
253095
253112
  const rootPackageName = getRootPackageName(rawImportPath);
253096
253113
  if (!rootPackageName ||
253097
- !allowedPackages.includes(rootPackageName) ||
253114
+ !allowedPackages.has(rootPackageName) ||
253098
253115
  (!isStrictMode &&
253099
253116
  isAlreadyNestedImport(rawImportPath, rootPackageName))) {
253100
253117
  return;
@@ -253103,32 +253120,38 @@ const rule$1 = createRule({
253103
253120
  if (importedSymbols.length === 0) {
253104
253121
  return;
253105
253122
  }
253106
- const currentFileName = context.getFilename();
253107
- const rootEntryDirectory = resolveRootEntryDirectory(rawImportPath, currentFileName, program);
253123
+ const currentState = getState();
253124
+ const rootEntryDirectory = getCachedRootEntryDirectory({
253125
+ fromFile: context.getFilename(),
253126
+ importPath: rawImportPath,
253127
+ state: currentState,
253128
+ });
253108
253129
  if (!rootEntryDirectory) {
253109
- context.report({
253110
- messageId: MESSAGE_ID,
253111
- node,
253112
- });
253113
253130
  return;
253114
253131
  }
253115
- const nestedEntryPointRelativePaths = findNestedEntryPointRelativePaths(rootEntryDirectory);
253132
+ const nestedEntryPointRelativePaths = getCachedNestedEntryPointRelativePaths(rootEntryDirectory, currentState.shared.nestedEntryPointPathsByRoot);
253116
253133
  if (nestedEntryPointRelativePaths.length === 0) {
253117
- context.report({
253118
- messageId: MESSAGE_ID,
253119
- node,
253120
- });
253121
253134
  return;
253122
253135
  }
253123
- const candidateEntryPointPaths = selectCandidateEntryPointsForMode(nestedEntryPointRelativePaths, isStrictMode);
253136
+ const candidateEntryPointPaths = selectCandidateEntryPointsForMode(nestedEntryPointRelativePaths, currentState.isStrictMode);
253124
253137
  if (candidateEntryPointPaths.length === 0) {
253125
253138
  return;
253126
253139
  }
253127
- const symbolToEntryPoint = mapSymbolsToEntryPointsUsingTypeChecker(importedSymbols, candidateEntryPointPaths, rootEntryDirectory, program, typeChecker);
253140
+ const symbolToEntryPoint = mapSymbolsToEntryPointsUsingTypeChecker({
253141
+ candidateEntryPoints: candidateEntryPointPaths,
253142
+ importedSymbols,
253143
+ rootEntryDirectory,
253144
+ state: currentState,
253145
+ });
253128
253146
  if (symbolToEntryPoint.size === 0) {
253129
253147
  return;
253130
253148
  }
253131
- const newImportBlock = buildRewrittenImports(node, rawImportPath, symbolToEntryPoint);
253149
+ const newImportBlock = buildRewrittenImports({
253150
+ baseImportPath: rawImportPath,
253151
+ node,
253152
+ state: currentState,
253153
+ symbolToEntryPoint,
253154
+ });
253132
253155
  context.report({
253133
253156
  fix(fixer) {
253134
253157
  return fixer.replaceTextRange(node.range, newImportBlock);
@@ -253153,7 +253176,15 @@ const rule$1 = createRule({
253153
253176
  {
253154
253177
  additionalProperties: false,
253155
253178
  properties: {
253156
- importFilter: { type: ['string', 'array'] },
253179
+ importFilter: {
253180
+ oneOf: [
253181
+ { type: 'string' },
253182
+ {
253183
+ items: { type: 'string' },
253184
+ type: 'array',
253185
+ },
253186
+ ],
253187
+ },
253157
253188
  strict: { type: 'boolean' },
253158
253189
  },
253159
253190
  type: 'object',
@@ -253163,22 +253194,22 @@ const rule$1 = createRule({
253163
253194
  },
253164
253195
  name: 'prefer-deep-imports',
253165
253196
  });
253166
- /**
253167
- * Normalize "importFilter" option to a flat array of package names.
253168
- * The rule expects concrete package names, not regular expression strings.
253169
- */
253197
+ function getSharedState(program) {
253198
+ const cached = sharedStateByProgram.get(program);
253199
+ if (cached) {
253200
+ return cached;
253201
+ }
253202
+ const state = {
253203
+ entryPointBySymbolCache: new Map(),
253204
+ nestedEntryPointPathsByRoot: new Map(),
253205
+ rootEntryDirectoryByImport: new Map(),
253206
+ };
253207
+ sharedStateByProgram.set(program, state);
253208
+ return state;
253209
+ }
253170
253210
  function normalizeImportFilter(importFilter) {
253171
- return Array.isArray(importFilter) ? importFilter : [importFilter];
253211
+ return (Array.isArray(importFilter) ? importFilter : [importFilter]).filter(Boolean);
253172
253212
  }
253173
- /**
253174
- * Extract the package root name from an import specifier.
253175
- *
253176
- * Examples:
253177
- * "@taiga-ui/core" → "@taiga-ui/core"
253178
- * "@taiga-ui/core/components" → "@taiga-ui/core"
253179
- * "some-lib" → "some-lib"
253180
- * "some-lib/utils" → "some-lib"
253181
- */
253182
253213
  function getRootPackageName(importPath) {
253183
253214
  if (importPath.startsWith('@')) {
253184
253215
  const segments = importPath.split('/');
@@ -253190,15 +253221,6 @@ function getRootPackageName(importPath) {
253190
253221
  const parts = importPath.split('/');
253191
253222
  return parts[0] ?? null;
253192
253223
  }
253193
- /**
253194
- * Check whether the current import path is already nested below the root package.
253195
- *
253196
- * Example:
253197
- * root = "@taiga-ui/core"
253198
- * "@taiga-ui/core" → false (root import)
253199
- * "@taiga-ui/core/components" → true
253200
- * "@taiga-ui/core/components/x" → true
253201
- */
253202
253224
  function isAlreadyNestedImport(importPath, rootPackageName) {
253203
253225
  if (!importPath.startsWith(rootPackageName)) {
253204
253226
  return false;
@@ -253207,81 +253229,49 @@ function isAlreadyNestedImport(importPath, rootPackageName) {
253207
253229
  const rootSegments = rootPackageName.split('/');
253208
253230
  return importSegments.length > rootSegments.length;
253209
253231
  }
253210
- /**
253211
- * Extract only named imported symbols:
253212
- *
253213
- * Examples:
253214
- * import {A, B as C} from 'x'; → ['A', 'B']
253215
- *
253216
- * Namespace imports and default imports are ignored for this rule.
253217
- */
253218
253232
  function extractNamedImportedSymbols(node) {
253219
253233
  return node.specifiers
253220
253234
  .filter((specifier) => specifier.type === dist$2.AST_NODE_TYPES.ImportSpecifier)
253221
- .map((specifier) => specifier.imported.type === dist$2.AST_NODE_TYPES.Identifier
253235
+ .map(getImportedName);
253236
+ }
253237
+ function getImportedName(specifier) {
253238
+ return specifier.imported.type === dist$2.AST_NODE_TYPES.Identifier
253222
253239
  ? specifier.imported.name
253223
- : specifier.imported.value);
253240
+ : specifier.imported.value;
253241
+ }
253242
+ function getCachedRootEntryDirectory({ fromFile, importPath, state, }) {
253243
+ const cacheKey = `${path.dirname(fromFile)}\0${importPath}`;
253244
+ const cache = state.shared.rootEntryDirectoryByImport;
253245
+ if (cache.has(cacheKey)) {
253246
+ return cache.get(cacheKey) ?? null;
253247
+ }
253248
+ const rootEntryDirectory = resolveRootEntryDirectory({
253249
+ fromFile,
253250
+ importPath,
253251
+ program: state.program,
253252
+ });
253253
+ cache.set(cacheKey, rootEntryDirectory);
253254
+ return rootEntryDirectory;
253224
253255
  }
253225
- /**
253226
- * Resolve the physical directory of the module being imported.
253227
- * We rely on the same module resolution that TypeScript uses for the program.
253228
- */
253229
- function resolveRootEntryDirectory(importPath, fromFile, program) {
253230
- const compilerOptions = program.getCompilerOptions();
253231
- const resolution = ts.resolveModuleName(importPath, fromFile, compilerOptions, ts.sys).resolvedModule;
253232
- if (!resolution) {
253233
- return null;
253256
+ function resolveRootEntryDirectory({ fromFile, importPath, program, }) {
253257
+ const resolution = ts.resolveModuleName(importPath, fromFile, program.getCompilerOptions(), ts.sys).resolvedModule;
253258
+ return resolution ? path.dirname(resolution.resolvedFileName) : null;
253259
+ }
253260
+ function getCachedNestedEntryPointRelativePaths(rootEntryDirectory, cache) {
253261
+ if (cache.has(rootEntryDirectory)) {
253262
+ return cache.get(rootEntryDirectory) ?? [];
253234
253263
  }
253235
- return path.dirname(resolution.resolvedFileName);
253264
+ const entryPoints = findNestedEntryPointRelativePaths(rootEntryDirectory);
253265
+ cache.set(rootEntryDirectory, entryPoints);
253266
+ return entryPoints;
253236
253267
  }
253237
- /**
253238
- * Find all nested entry points relative to the given root directory.
253239
- *
253240
- * A directory is considered a nested entry point if it contains either:
253241
- * - "ng-package.json" (Angular library entry)
253242
- * - "collection.json" (Angular schematics collection)
253243
- *
253244
- * Returned paths are relative to "rootEntryDirectory".
253245
- *
253246
- * Example:
253247
- * rootEntryDirectory = ".../core/src"
253248
- * found:
253249
- * "utils/ng-package.json" → "utils"
253250
- * "utils/dom/ng-package.json" → "utils/dom"
253251
- * "schematics/collection.json" → "schematics"
253252
- */
253253
253268
  function findNestedEntryPointRelativePaths(rootEntryDirectory) {
253254
- const ngPackageJsonFiles = globSync('**/ng-package.json', {
253255
- absolute: false,
253256
- cwd: rootEntryDirectory,
253257
- });
253258
- const collectionJsonFiles = globSync('**/collection.json', {
253259
- absolute: false,
253260
- cwd: rootEntryDirectory,
253261
- });
253262
- const directories = [];
253263
- for (const file of ngPackageJsonFiles) {
253264
- const normalized = file.replaceAll('\\', '/').replace(/\/ng-package\.json$/, '');
253265
- if (normalized && normalized !== '.') {
253266
- directories.push(normalized);
253267
- }
253268
- }
253269
- for (const file of collectionJsonFiles) {
253270
- const normalized = file.replaceAll('\\', '/').replace(/\/collection\.json$/, '');
253271
- if (normalized && normalized !== '.') {
253272
- directories.push(normalized);
253273
- }
253274
- }
253275
- return directories;
253269
+ const files = ['**/ng-package.json', '**/collection.json'].flatMap((pattern) => globSync(pattern, { cwd: rootEntryDirectory }));
253270
+ const directories = files
253271
+ .map((file) => path.posix.dirname(file.replaceAll('\\', '/')))
253272
+ .filter((directory) => directory && directory !== '.');
253273
+ return [...new Set(directories)];
253276
253274
  }
253277
- /**
253278
- * For strict = false:
253279
- * Only first-level nested directories are candidates.
253280
- *
253281
- * For strict = true:
253282
- * All nested directories are candidates, sorted from deepest to shallowest
253283
- * so that the deepest match wins.
253284
- */
253285
253275
  function selectCandidateEntryPointsForMode(allNestedRelativePaths, strict) {
253286
253276
  if (!strict) {
253287
253277
  return allNestedRelativePaths.filter((relativePath) => !relativePath.includes('/'));
@@ -253292,62 +253282,70 @@ function selectCandidateEntryPointsForMode(allNestedRelativePaths, strict) {
253292
253282
  return depthB - depthA;
253293
253283
  });
253294
253284
  }
253295
- /**
253296
- * Build a map from exported symbol name to nested entry point relative path.
253297
- *
253298
- * Implementation strategy:
253299
- * 1. For each candidate nested entry point:
253300
- * - Determine its entry file (using ng-package.json or collection.json).
253301
- * - Ask TypeScript for the module symbol and its exports.
253302
- * 2. For each imported symbol:
253303
- * - Find the first entry point whose export table contains that symbol.
253304
- * - strict = true: candidates were sorted deepest-first.
253305
- * - strict = false: candidates contain only first-level nested entry points.
253306
- */
253307
- function mapSymbolsToEntryPointsUsingTypeChecker(importedSymbols, candidateEntryPoints, rootEntryDirectory, program, typeChecker) {
253285
+ function mapSymbolsToEntryPointsUsingTypeChecker({ candidateEntryPoints, importedSymbols, rootEntryDirectory, state, }) {
253308
253286
  const symbolToEntryPoint = new Map();
253309
- const exportTableByEntryPoint = new Map();
253310
- for (const relativeEntryDir of candidateEntryPoints) {
253311
- const entryFile = getEntryFileForNestedEntryPoint(rootEntryDirectory, relativeEntryDir);
253312
- if (!entryFile) {
253313
- continue;
253314
- }
253315
- const sourceFile = program.getSourceFile(entryFile);
253316
- if (!sourceFile) {
253317
- continue;
253287
+ const entryPointBySymbol = getCachedEntryPointBySymbol({
253288
+ candidateEntryPoints,
253289
+ rootEntryDirectory,
253290
+ state,
253291
+ });
253292
+ for (const importedSymbol of importedSymbols) {
253293
+ const entryPoint = entryPointBySymbol.get(importedSymbol);
253294
+ if (entryPoint) {
253295
+ symbolToEntryPoint.set(importedSymbol, entryPoint);
253318
253296
  }
253319
- const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
253320
- if (!moduleSymbol) {
253297
+ }
253298
+ return symbolToEntryPoint;
253299
+ }
253300
+ function getCachedEntryPointBySymbol({ candidateEntryPoints, rootEntryDirectory, state, }) {
253301
+ const cacheKey = `${rootEntryDirectory}\0${candidateEntryPoints.join('\0')}`;
253302
+ const cache = state.shared.entryPointBySymbolCache;
253303
+ if (cache.has(cacheKey)) {
253304
+ return cache.get(cacheKey) ?? new Map();
253305
+ }
253306
+ const entryPointBySymbol = buildEntryPointBySymbolIndex({
253307
+ candidateEntryPoints,
253308
+ rootEntryDirectory,
253309
+ state,
253310
+ });
253311
+ cache.set(cacheKey, entryPointBySymbol);
253312
+ return entryPointBySymbol;
253313
+ }
253314
+ function buildEntryPointBySymbolIndex({ candidateEntryPoints, rootEntryDirectory, state, }) {
253315
+ const entryPointBySymbol = new Map();
253316
+ for (const relativeEntryDir of candidateEntryPoints) {
253317
+ const exportedNames = getExportedNamesForEntryPoint({
253318
+ relativeEntryDirectory: relativeEntryDir,
253319
+ rootEntryDirectory,
253320
+ state,
253321
+ });
253322
+ if (!exportedNames) {
253321
253323
  continue;
253322
253324
  }
253323
- const exports$1 = typeChecker.getExportsOfModule(moduleSymbol);
253324
- const exportedNames = new Set(exports$1.map((symbol) => symbol.getName()));
253325
- exportTableByEntryPoint.set(relativeEntryDir, exportedNames);
253326
- }
253327
- for (const importedSymbol of importedSymbols) {
253328
- for (const relativeEntryDir of candidateEntryPoints) {
253329
- const exportedNames = exportTableByEntryPoint.get(relativeEntryDir);
253330
- if (!exportedNames?.has(importedSymbol)) {
253331
- continue;
253325
+ for (const exportedName of exportedNames) {
253326
+ if (!entryPointBySymbol.has(exportedName)) {
253327
+ entryPointBySymbol.set(exportedName, relativeEntryDir);
253332
253328
  }
253333
- symbolToEntryPoint.set(importedSymbol, relativeEntryDir);
253334
- break;
253335
253329
  }
253336
253330
  }
253337
- return symbolToEntryPoint;
253331
+ return entryPointBySymbol;
253332
+ }
253333
+ function getExportedNamesForEntryPoint({ relativeEntryDirectory, rootEntryDirectory, state, }) {
253334
+ const entryFile = getEntryFileForNestedEntryPoint(rootEntryDirectory, relativeEntryDirectory);
253335
+ if (!entryFile) {
253336
+ return null;
253337
+ }
253338
+ const sourceFile = state.program.getSourceFile(entryFile);
253339
+ if (!sourceFile) {
253340
+ return null;
253341
+ }
253342
+ const moduleSymbol = state.typeChecker.getSymbolAtLocation(sourceFile);
253343
+ if (!moduleSymbol) {
253344
+ return null;
253345
+ }
253346
+ const exports$1 = state.typeChecker.getExportsOfModule(moduleSymbol);
253347
+ return new Set(exports$1.map((symbol) => symbol.getName()));
253338
253348
  }
253339
- /**
253340
- * Determine the physical entry file for a nested entry point.
253341
- *
253342
- * Priority:
253343
- * 1. If "ng-package.json" exists:
253344
- * - use lib.entryFile if present
253345
- * - otherwise fall back to "index.ts"
253346
- * 2. Else if "collection.json" exists:
253347
- * - treat this directory as a schematic collection package
253348
- * - entry file is "index.ts" if it exists
253349
- * 3. Otherwise: no entry file can be determined → return null
253350
- */
253351
253349
  function getEntryFileForNestedEntryPoint(rootEntryDirectory, relativeEntryDirectory) {
253352
253350
  const absoluteDirectory = path.join(rootEntryDirectory, relativeEntryDirectory);
253353
253351
  const ngPackageJsonPath = path.join(absoluteDirectory, 'ng-package.json');
@@ -253370,58 +253368,74 @@ function getEntryFileForNestedEntryPoint(rootEntryDirectory, relativeEntryDirect
253370
253368
  }
253371
253369
  return null;
253372
253370
  }
253373
- /**
253374
- * Build the final text block with rewritten import declarations.
253375
- *
253376
- * Example:
253377
- * original:
253378
- * import {A, B as C, D} from '@taiga-ui/core';
253379
- *
253380
- * symbolMap (strict = true):
253381
- * A -> "components/button"
253382
- * B -> "components/button"
253383
- * D -> "components/other"
253384
- *
253385
- * result:
253386
- * import {A, B as C} from '@taiga-ui/core/components/button';
253387
- * import {D} from '@taiga-ui/core/components/other';
253388
- */
253389
- function buildRewrittenImports(node, baseImportPath, symbolToEntryPoint) {
253390
- const isTypeOnlyImport = node.importKind === 'type';
253371
+ function buildRewrittenImports({ baseImportPath, node, state, symbolToEntryPoint, }) {
253391
253372
  const groupedByTarget = new Map();
253392
- for (const [symbolName, relativeEntryPath] of symbolToEntryPoint.entries()) {
253373
+ const remainingSpecifiers = [];
253374
+ for (const specifier of node.specifiers) {
253375
+ if (specifier.type !== dist$2.AST_NODE_TYPES.ImportSpecifier) {
253376
+ remainingSpecifiers.push(specifier);
253377
+ continue;
253378
+ }
253379
+ const importedName = getImportedName(specifier);
253380
+ const relativeEntryPath = symbolToEntryPoint.get(importedName);
253381
+ if (!relativeEntryPath) {
253382
+ remainingSpecifiers.push(specifier);
253383
+ continue;
253384
+ }
253393
253385
  const targetSpecifier = `${baseImportPath}/${relativeEntryPath}`;
253394
253386
  if (!groupedByTarget.has(targetSpecifier)) {
253395
253387
  groupedByTarget.set(targetSpecifier, []);
253396
253388
  }
253397
- const targetGroup = groupedByTarget.get(targetSpecifier);
253398
- if (targetGroup) {
253399
- targetGroup.push(symbolName);
253400
- }
253389
+ groupedByTarget.get(targetSpecifier)?.push(specifier);
253401
253390
  }
253402
253391
  const importStatements = [];
253403
- for (const [targetSpecifier, symbols] of groupedByTarget.entries()) {
253404
- const parts = symbols.map((symbolName) => {
253405
- const matchingSpecifier = node.specifiers.find((specifier) => specifier.type === dist$2.AST_NODE_TYPES.ImportSpecifier &&
253406
- (specifier.imported.type === dist$2.AST_NODE_TYPES.Identifier
253407
- ? specifier.imported.name === symbolName
253408
- : specifier.imported.value === symbolName));
253409
- if (!matchingSpecifier) {
253410
- return symbolName;
253411
- }
253412
- const importedName = matchingSpecifier.imported.type === dist$2.AST_NODE_TYPES.Identifier
253413
- ? matchingSpecifier.imported.name
253414
- : matchingSpecifier.imported.value;
253415
- const localName = matchingSpecifier.local.name;
253416
- return importedName === localName
253417
- ? importedName
253418
- : `${importedName} as ${localName}`;
253419
- });
253420
- const statement = `import ${isTypeOnlyImport ? 'type ' : ''}{${parts.join(', ')}} from '${targetSpecifier}';`;
253421
- importStatements.push(statement);
253392
+ for (const [targetSpecifier, specifiers] of groupedByTarget.entries()) {
253393
+ importStatements.push(buildNamedImportStatement({
253394
+ importKind: node.importKind,
253395
+ importPath: targetSpecifier,
253396
+ specifiers,
253397
+ state,
253398
+ }));
253399
+ }
253400
+ const remainingImportStatement = buildImportStatement({
253401
+ importKind: node.importKind,
253402
+ importPath: baseImportPath,
253403
+ specifiers: remainingSpecifiers,
253404
+ state,
253405
+ });
253406
+ if (remainingImportStatement) {
253407
+ importStatements.push(remainingImportStatement);
253422
253408
  }
253423
253409
  return importStatements.join('\n');
253424
253410
  }
253411
+ function buildNamedImportStatement({ importKind, importPath, specifiers, state, }) {
253412
+ const importKeyword = importKind === 'type' ? 'import type' : 'import';
253413
+ const parts = specifiers.map((specifier) => state.sourceCode.getText(specifier));
253414
+ return `${importKeyword} {${parts.join(', ')}} from '${importPath}';`;
253415
+ }
253416
+ function buildImportStatement({ importKind, importPath, specifiers, state, }) {
253417
+ if (specifiers.length === 0) {
253418
+ return null;
253419
+ }
253420
+ const importKeyword = importKind === 'type' ? 'import type' : 'import';
253421
+ const defaultSpecifier = specifiers.find((specifier) => specifier.type === dist$2.AST_NODE_TYPES.ImportDefaultSpecifier);
253422
+ const namespaceSpecifier = specifiers.find((specifier) => specifier.type === dist$2.AST_NODE_TYPES.ImportNamespaceSpecifier);
253423
+ const namedSpecifiers = specifiers.filter((specifier) => specifier.type === dist$2.AST_NODE_TYPES.ImportSpecifier);
253424
+ const clauses = [];
253425
+ if (defaultSpecifier) {
253426
+ clauses.push(state.sourceCode.getText(defaultSpecifier));
253427
+ }
253428
+ if (namespaceSpecifier) {
253429
+ clauses.push(state.sourceCode.getText(namespaceSpecifier));
253430
+ }
253431
+ if (namedSpecifiers.length > 0) {
253432
+ clauses.push(`{${namedSpecifiers.map((specifier) => state.sourceCode.getText(specifier)).join(', ')}}`);
253433
+ }
253434
+ if (clauses.length === 0) {
253435
+ return null;
253436
+ }
253437
+ return `${importKeyword} ${clauses.join(', ')} from '${importPath}';`;
253438
+ }
253425
253439
 
253426
253440
  function getTypeName(param) {
253427
253441
  const typeAnnotation = param?.parameter?.typeAnnotation ?? param?.typeAnnotation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.496.0",
3
+ "version": "0.498.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,14 +27,13 @@
27
27
  ],
28
28
  "type": "module",
29
29
  "devDependencies": {
30
- "glob": "13.0.6"
30
+ "@typescript-eslint/rule-tester": "8.59.1"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@eslint/compat": "^2.0.5",
34
34
  "@eslint/markdown": "^8.0.1",
35
35
  "@smarttools/eslint-plugin-rxjs": "^1.0.22",
36
36
  "@stylistic/eslint-plugin": "^5.10.0",
37
- "@types/glob": "*",
38
37
  "angular-eslint": "^20.7.0",
39
38
  "eslint": "^9.39.4",
40
39
  "eslint-config-prettier": "^10.1.7",
@@ -55,9 +54,8 @@
55
54
  "eslint-plugin-sonarjs": "^4.0.3",
56
55
  "eslint-plugin-unicorn": "^64.0.0",
57
56
  "eslint-plugin-unused-imports": "^4.4.1",
58
- "glob": "*",
59
57
  "globals": "^17.5.0",
60
- "typescript-eslint": "^8.59.0"
58
+ "typescript-eslint": "^8.59.1"
61
59
  },
62
60
  "publishConfig": {
63
61
  "access": "public"