@taiga-ui/eslint-plugin-experience-next 0.383.0 → 0.384.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 +127 -27
  2. package/package.json +4 -4
package/index.esm.js CHANGED
@@ -1749,54 +1749,154 @@ var noDeepImportsToIndexedPackages = createRule$5({
1749
1749
  const parserServices = ESLintUtils.getParserServices(context);
1750
1750
  const program = parserServices.program;
1751
1751
  const compilerHost = ts.createCompilerHost(program.getCompilerOptions(), true);
1752
+ function resolveTypescriptModule(moduleSpecifier) {
1753
+ const resolved = ts.resolveModuleName(moduleSpecifier, context.filename, program.getCompilerOptions(), compilerHost);
1754
+ return resolved.resolvedModule?.resolvedFileName ?? null;
1755
+ }
1752
1756
  return {
1753
1757
  ImportDeclaration(node) {
1754
- const importPath = node.source.value;
1755
- if (typeof importPath !== 'string' || importPath.startsWith('.')) {
1758
+ const importSpecifier = node.source.value;
1759
+ if (typeof importSpecifier !== 'string') {
1756
1760
  return;
1757
1761
  }
1758
- const containingFile = context.filename;
1759
- const { resolvedModule } = ts.resolveModuleName(importPath, containingFile, program.getCompilerOptions(), compilerHost);
1760
- const resolvedPath = resolvedModule?.resolvedFileName;
1761
- if (!resolvedPath || resolvedPath.endsWith('index.ts')) {
1762
+ if (!isExternalModuleSpecifier(importSpecifier)) {
1762
1763
  return;
1763
1764
  }
1764
- const dir = path.dirname(resolvedPath);
1765
- const baseDir = path.resolve(dir, '..');
1766
- const indexPath = path.join(baseDir, 'index.ts');
1767
- const ngPackagePath = path.join(baseDir, 'ng-package.json');
1768
- const packageJsonPath = path.join(baseDir, 'package.json');
1769
- const hasIndex = fs.existsSync(indexPath);
1770
- const hasPackage = fs.existsSync(ngPackagePath) || fs.existsSync(packageJsonPath);
1771
- if (hasIndex && hasPackage) {
1772
- const relative = path.relative(baseDir, resolvedPath);
1773
- const shouldFix = !!(relative && !relative.startsWith('..'));
1774
- if (shouldFix) {
1775
- const parts = importPath.split('/');
1776
- const suggestedImport = parts.slice(0, -1).join('/');
1777
- context.report({
1778
- data: { importPath, suggestedImport },
1779
- messageId: 'deepImport',
1780
- node: node.source,
1781
- });
1782
- }
1765
+ const packageRootSpecifier = getPackageRootSpecifier(importSpecifier);
1766
+ const importSubpath = getSubpath(importSpecifier, packageRootSpecifier);
1767
+ if (!importSubpath) {
1768
+ return;
1769
+ }
1770
+ const resolvedRootModuleFilePath = resolveTypescriptModule(packageRootSpecifier);
1771
+ if (!resolvedRootModuleFilePath) {
1772
+ return;
1773
+ }
1774
+ const packageMarkerFilePath = pickPackageMarkerFile(resolvedRootModuleFilePath);
1775
+ if (!packageMarkerFilePath) {
1776
+ return;
1777
+ }
1778
+ const packageDirectory = path.dirname(packageMarkerFilePath);
1779
+ const indexFilePath = pickIndexFileInDirectory(packageDirectory);
1780
+ if (!indexFilePath) {
1781
+ return;
1782
+ }
1783
+ const hasMatchingReExport = indexExportsSubpath(indexFilePath, importSubpath);
1784
+ if (!hasMatchingReExport) {
1785
+ return;
1783
1786
  }
1787
+ context.report({
1788
+ data: {
1789
+ importPath: importSpecifier,
1790
+ suggestedImport: packageRootSpecifier,
1791
+ },
1792
+ messageId: 'deepImport',
1793
+ node: node.source,
1794
+ });
1784
1795
  },
1785
1796
  };
1786
1797
  },
1787
1798
  defaultOptions: [],
1788
1799
  meta: {
1789
1800
  docs: {
1790
- description: 'Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json',
1801
+ description: 'Disallow deep imports only when package root index.ts (or index.d.ts) re-exports that subpath, and the package is marked by ng-package.json or package.json',
1791
1802
  },
1792
1803
  messages: {
1793
- deepImport: 'Import "{{importPath}}" should go through the package index.ts (use "{{suggestedImport}}").',
1804
+ deepImport: 'Import "{{importPath}}" should go through the package root export (use "{{suggestedImport}}").',
1794
1805
  },
1795
1806
  schema: [],
1796
1807
  type: 'problem',
1797
1808
  },
1798
1809
  name: 'no-deep-imports-to-indexed-packages',
1799
1810
  });
1811
+ function isExternalModuleSpecifier(moduleSpecifier) {
1812
+ if (!moduleSpecifier || moduleSpecifier.startsWith('.')) {
1813
+ return false;
1814
+ }
1815
+ return !path.isAbsolute(moduleSpecifier);
1816
+ }
1817
+ function pickPackageMarkerFile(resolvedRootFilePath) {
1818
+ const resolvedRootDirectory = path.dirname(resolvedRootFilePath);
1819
+ const nearestNgPackageJson = findNearestFileUpwards(resolvedRootDirectory, 'ng-package.json');
1820
+ const nearestPackageJson = findNearestFileUpwards(resolvedRootDirectory, 'package.json');
1821
+ return nearestNgPackageJson ?? nearestPackageJson ?? null;
1822
+ }
1823
+ function pickIndexFileInDirectory(packageDirectory) {
1824
+ const indexTypescriptPath = path.join(packageDirectory, 'index.ts');
1825
+ const indexTypesDeclarationPath = path.join(packageDirectory, 'index.d.ts');
1826
+ if (fs.existsSync(indexTypescriptPath)) {
1827
+ return indexTypescriptPath;
1828
+ }
1829
+ if (fs.existsSync(indexTypesDeclarationPath)) {
1830
+ return indexTypesDeclarationPath;
1831
+ }
1832
+ return null;
1833
+ }
1834
+ function isScopedPackage(importSpecifier) {
1835
+ return importSpecifier.startsWith('@');
1836
+ }
1837
+ function getPackageRootSpecifier(importSpecifier) {
1838
+ const pathParts = importSpecifier.split('/');
1839
+ if (isScopedPackage(importSpecifier)) {
1840
+ if (pathParts.length >= 2) {
1841
+ return `${pathParts[0]}/${pathParts[1]}`;
1842
+ }
1843
+ return importSpecifier;
1844
+ }
1845
+ return pathParts[0] ?? importSpecifier;
1846
+ }
1847
+ function getSubpath(importSpecifier, packageRootSpecifier) {
1848
+ if (importSpecifier === packageRootSpecifier) {
1849
+ return null;
1850
+ }
1851
+ if (!importSpecifier.startsWith(`${packageRootSpecifier}/`)) {
1852
+ return null;
1853
+ }
1854
+ return importSpecifier.slice(packageRootSpecifier.length + 1);
1855
+ }
1856
+ function findNearestFileUpwards(startDirectory, fileName) {
1857
+ let currentDirectory = startDirectory;
1858
+ while (currentDirectory.length > 0) {
1859
+ const candidatePath = path.join(currentDirectory, fileName);
1860
+ if (fs.existsSync(candidatePath)) {
1861
+ return candidatePath;
1862
+ }
1863
+ const parentDirectory = path.dirname(currentDirectory);
1864
+ if (parentDirectory === currentDirectory) {
1865
+ break;
1866
+ }
1867
+ currentDirectory = parentDirectory;
1868
+ }
1869
+ return null;
1870
+ }
1871
+ function normalizeModuleSpecifier(moduleSpecifier) {
1872
+ return moduleSpecifier.replaceAll('\\', '/');
1873
+ }
1874
+ function stripKnownExtensions(filePathOrSpecifier) {
1875
+ return filePathOrSpecifier.replace(/\.(d\.ts|ts|tsx|js|jsx|mjs|cjs)$/, '');
1876
+ }
1877
+ function indexExportsSubpath(indexFilePath, subpath) {
1878
+ const fileText = fs.readFileSync(indexFilePath, 'utf8');
1879
+ const sourceFile = ts.createSourceFile(indexFilePath, fileText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1880
+ const expectedSpecifier = normalizeModuleSpecifier(stripKnownExtensions(`./${subpath}`));
1881
+ const expectedIndexSpecifier = normalizeModuleSpecifier(stripKnownExtensions(`./${subpath}/index`));
1882
+ let isReExportFound = false;
1883
+ sourceFile.forEachChild((astNode) => {
1884
+ if (isReExportFound) {
1885
+ return;
1886
+ }
1887
+ if (ts.isExportDeclaration(astNode)) {
1888
+ const moduleSpecifierNode = astNode.moduleSpecifier;
1889
+ if (moduleSpecifierNode && ts.isStringLiteral(moduleSpecifierNode)) {
1890
+ const exportedSpecifier = normalizeModuleSpecifier(stripKnownExtensions(moduleSpecifierNode.text));
1891
+ if (exportedSpecifier === expectedSpecifier ||
1892
+ exportedSpecifier === expectedIndexSpecifier) {
1893
+ isReExportFound = true;
1894
+ }
1895
+ }
1896
+ }
1897
+ });
1898
+ return isReExportFound;
1899
+ }
1800
1900
 
1801
1901
  const MESSAGE_ID$2 = 'no-href-with-router-link';
1802
1902
  const ERROR_MESSAGE$1 = 'Do not use href and routerLink attributes together on the same element';
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.383.0",
3
+ "version": "0.384.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",
7
7
  "devDependencies": {
8
- "@typescript-eslint/rule-tester": "8.50.0",
8
+ "@typescript-eslint/rule-tester": "8.53.0",
9
9
  "glob": "13.0.0"
10
10
  },
11
11
  "peerDependencies": {
@@ -16,7 +16,7 @@
16
16
  "@smarttools/eslint-plugin-rxjs": "^1.0.22",
17
17
  "@stylistic/eslint-plugin": "^5.6.1",
18
18
  "@types/glob": "*",
19
- "@typescript-eslint/eslint-plugin": "^8.50.0",
19
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
20
20
  "angular-eslint": "^20.7.0",
21
21
  "eslint": "^9.39.2",
22
22
  "eslint-config-prettier": "^10.1.7",
@@ -28,7 +28,7 @@
28
28
  "eslint-plugin-jest": "^29.5.0",
29
29
  "eslint-plugin-package-json": "^0.85.0",
30
30
  "eslint-plugin-perfectionist": "^4.15.1",
31
- "eslint-plugin-playwright": "^2.4.0",
31
+ "eslint-plugin-playwright": "^2.5.0",
32
32
  "eslint-plugin-prettier": "^5.5.4",
33
33
  "eslint-plugin-promise": "^7.2.1",
34
34
  "eslint-plugin-simple-import-sort": "^12.1.1",