@taiga-ui/eslint-plugin-experience-next 0.365.0 → 0.366.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/README.md CHANGED
@@ -42,6 +42,7 @@ export default [
42
42
  | array-as-const | Exported array of class references should be marked with `as const` | | 🔧 | |
43
43
  | class-property-naming | Enforce custom naming for class properties based on their type | | 🔧 | |
44
44
  | decorator-key-sort | Sorts the keys of the object passed to the `@Component/@Injectable/@NgModule/@Pipe` decorator | ✅ | 🔧 | |
45
+ | flat-exports | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
45
46
  | injection-token-description | They are required to provide a description for `InjectionToken` | ✅ | | |
46
47
  | no-deep-imports | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
47
48
  | no-deep-imports-to-indexed-packages | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | |
@@ -1,2 +1,2 @@
1
- declare const _default: import("typescript-eslint").FlatConfig.ConfigArray;
1
+ declare const _default: import("@eslint/config-helpers").Config[];
2
2
  export default _default;
package/index.d.ts CHANGED
@@ -13,6 +13,7 @@ declare const plugin: {
13
13
  'array-as-const': import("eslint").Rule.RuleModule;
14
14
  'class-property-naming': import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalidName", [import("./rules/class-property-naming").RuleConfig[]], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
15
15
  'decorator-key-sort': import("eslint").Rule.RuleModule;
16
+ 'flat-exports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"spreadArrays", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
16
17
  'injection-token-description': import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalid-injection-token-description", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
17
18
  'no-deep-imports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"no-deep-imports", [{
18
19
  currentProject: string;
package/index.esm.js CHANGED
@@ -1138,7 +1138,7 @@ const allPackageJSONs = globSync('**/package.json', {
1138
1138
  }).filter((path) => !readJSON(path).private);
1139
1139
  const packageNames = allPackageJSONs.map((path) => readJSON(path).name).filter(Boolean);
1140
1140
  const packageSourceGlobs = allPackageJSONs.map((p) => p.replaceAll(/\\+/g, '/').replace('package.json', '**/*.ts'));
1141
- var taigaSpecific = tseslint.config([
1141
+ var taigaSpecific = defineConfig([
1142
1142
  {
1143
1143
  files: packageSourceGlobs,
1144
1144
  ignores: ['**/*.spec.ts', '**/*.cy.ts'],
@@ -1176,6 +1176,7 @@ var taigaSpecific = tseslint.config([
1176
1176
  },
1177
1177
  ],
1178
1178
  ],
1179
+ '@taiga-ui/experience-next/flat-exports': 'error',
1179
1180
  '@taiga-ui/experience-next/strict-tui-doc-example': 'error',
1180
1181
  '@typescript-eslint/naming-convention': [
1181
1182
  'error',
@@ -1301,8 +1302,8 @@ function intersect(a, b) {
1301
1302
  return a.some((type) => origin.has(type));
1302
1303
  }
1303
1304
 
1304
- const createRule$6 = ESLintUtils.RuleCreator((name) => name);
1305
- var classPropertyNaming = createRule$6({
1305
+ const createRule$7 = ESLintUtils.RuleCreator((name) => name);
1306
+ var classPropertyNaming = createRule$7({
1306
1307
  create(context, [configs]) {
1307
1308
  const parserServices = ESLintUtils.getParserServices(context);
1308
1309
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1431,6 +1432,173 @@ function getCorrectOrderRelative(correct, current) {
1431
1432
  return correct.filter((item) => current.includes(item));
1432
1433
  }
1433
1434
 
1435
+ function getConstArray(node) {
1436
+ if (!node || node.type !== AST_NODE_TYPES$1.TSAsExpression) {
1437
+ return null;
1438
+ }
1439
+ const annotation = node.typeAnnotation;
1440
+ const isConst = annotation.type === AST_NODE_TYPES$1.TSTypeReference &&
1441
+ annotation.typeName.type === AST_NODE_TYPES$1.Identifier &&
1442
+ annotation.typeName.name === 'const';
1443
+ if (!isConst) {
1444
+ return null;
1445
+ }
1446
+ if (node.expression.type === AST_NODE_TYPES$1.ArrayExpression) {
1447
+ return node.expression;
1448
+ }
1449
+ return null;
1450
+ }
1451
+
1452
+ function isClassType(type) {
1453
+ const symbol = type.getSymbol();
1454
+ if (!symbol) {
1455
+ return false;
1456
+ }
1457
+ return (symbol
1458
+ .getDeclarations()
1459
+ ?.some((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)) ?? false);
1460
+ }
1461
+
1462
+ function isExternalPureTuple(typeChecker, type) {
1463
+ if (!typeChecker.isTupleType(type)) {
1464
+ return false;
1465
+ }
1466
+ const typeRef = type;
1467
+ const typeArgs = typeChecker.getTypeArguments(typeRef);
1468
+ if (typeArgs.length === 0) {
1469
+ return false;
1470
+ }
1471
+ return typeArgs.every((item) => isClassType(item));
1472
+ }
1473
+
1474
+ const createRule$6 = ESLintUtils.RuleCreator((name) => name);
1475
+ const MESSAGE_ID$5 = 'spreadArrays';
1476
+ var flatExports = createRule$6({
1477
+ create(context) {
1478
+ const parserServices = ESLintUtils.getParserServices(context);
1479
+ const typeChecker = parserServices.program.getTypeChecker();
1480
+ const arrays = new Map();
1481
+ const purityCache = new WeakMap();
1482
+ const isPureArray = (arr) => {
1483
+ if (purityCache.has(arr)) {
1484
+ return purityCache.get(arr);
1485
+ }
1486
+ if (arr.isDirty) {
1487
+ purityCache.set(arr, false);
1488
+ return false;
1489
+ }
1490
+ for (const el of arr.elements) {
1491
+ if (el.isClass) {
1492
+ continue;
1493
+ }
1494
+ if (el.isArrayLike) {
1495
+ const nested = arrays.get(el.name);
1496
+ if (nested) {
1497
+ if (!isPureArray(nested)) {
1498
+ purityCache.set(arr, false);
1499
+ return false;
1500
+ }
1501
+ continue;
1502
+ }
1503
+ if (isExternalPureTuple(typeChecker, el.type)) {
1504
+ continue;
1505
+ }
1506
+ purityCache.set(arr, false);
1507
+ return false;
1508
+ }
1509
+ purityCache.set(arr, false);
1510
+ return false;
1511
+ }
1512
+ purityCache.set(arr, true);
1513
+ return true;
1514
+ };
1515
+ return {
1516
+ 'Program:exit'() {
1517
+ for (const [, arr] of arrays) {
1518
+ if (!isPureArray(arr)) {
1519
+ continue;
1520
+ }
1521
+ arr.elements.forEach((meta, index) => {
1522
+ if (!meta.isArrayLike) {
1523
+ return;
1524
+ }
1525
+ const elementNode = arr.node.elements[index];
1526
+ if (!elementNode ||
1527
+ elementNode.type !== AST_NODE_TYPES.Identifier) {
1528
+ return;
1529
+ }
1530
+ const hasLocalArrayMeta = arrays.has(meta.name);
1531
+ const isExternalPure = !hasLocalArrayMeta
1532
+ ? isExternalPureTuple(typeChecker, meta.type)
1533
+ : false;
1534
+ if (!hasLocalArrayMeta && !isExternalPure) {
1535
+ return;
1536
+ }
1537
+ context.report({
1538
+ data: { name: meta.name },
1539
+ fix(fixer) {
1540
+ return fixer.replaceText(elementNode, `...${meta.name}`);
1541
+ },
1542
+ messageId: MESSAGE_ID$5,
1543
+ node: elementNode,
1544
+ });
1545
+ });
1546
+ }
1547
+ },
1548
+ VariableDeclarator(node) {
1549
+ if (node.id.type !== AST_NODE_TYPES.Identifier) {
1550
+ return;
1551
+ }
1552
+ const name = node.id.name;
1553
+ const arrayExpression = getConstArray(node.init);
1554
+ if (!arrayExpression) {
1555
+ return;
1556
+ }
1557
+ const elements = [];
1558
+ let isDirty = false;
1559
+ for (const el of arrayExpression.elements) {
1560
+ if (!el || el.type !== AST_NODE_TYPES.Identifier) {
1561
+ isDirty = true;
1562
+ continue;
1563
+ }
1564
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(el);
1565
+ const elType = typeChecker.getTypeAtLocation(tsNode);
1566
+ const isClass = isClassType(elType);
1567
+ const isArrayLike = typeChecker.isArrayLikeType(elType) ||
1568
+ typeChecker.isTupleType(elType);
1569
+ elements.push({
1570
+ isArrayLike,
1571
+ isClass,
1572
+ name: el.name,
1573
+ type: elType,
1574
+ });
1575
+ if (!isClass && !isArrayLike) {
1576
+ isDirty = true;
1577
+ }
1578
+ }
1579
+ arrays.set(name, {
1580
+ elements,
1581
+ isDirty,
1582
+ node: arrayExpression,
1583
+ });
1584
+ },
1585
+ };
1586
+ },
1587
+ defaultOptions: [],
1588
+ meta: {
1589
+ docs: {
1590
+ description: 'Ensure exported const tuples contain spread arrays in pure entity chains.',
1591
+ },
1592
+ fixable: 'code',
1593
+ messages: {
1594
+ [MESSAGE_ID$5]: 'Spread "{{ name }}" to avoid nested arrays in exported entities.',
1595
+ },
1596
+ schema: [],
1597
+ type: 'suggestion',
1598
+ },
1599
+ name: 'flat-exports',
1600
+ });
1601
+
1434
1602
  const MESSAGE_ID$4 = 'invalid-injection-token-description';
1435
1603
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
1436
1604
  const createRule$5 = ESLintUtils.RuleCreator((name) => name);
@@ -2224,6 +2392,7 @@ const plugin = {
2224
2392
  'array-as-const': config$4,
2225
2393
  'class-property-naming': classPropertyNaming,
2226
2394
  'decorator-key-sort': config$3,
2395
+ 'flat-exports': flatExports,
2227
2396
  'injection-token-description': rule$4,
2228
2397
  'no-deep-imports': rule$3,
2229
2398
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.365.0",
3
+ "version": "0.366.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",
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"spreadArrays", [], unknown, ESLintUtils.RuleListener>;
3
+ export default _default;
@@ -0,0 +1,2 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ export declare function getConstArray(node: TSESTree.Expression | null): TSESTree.ArrayExpression | null;
@@ -0,0 +1,2 @@
1
+ import ts from 'typescript';
2
+ export declare function isClassType(type: ts.Type): boolean;
@@ -0,0 +1,2 @@
1
+ import { type Type, type TypeChecker } from 'typescript';
2
+ export declare function isExternalPureTuple(typeChecker: TypeChecker, type: Type): boolean;