@taiga-ui/eslint-plugin-experience-next 0.464.0 → 0.466.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.d.ts CHANGED
@@ -18,6 +18,7 @@ declare const plugin: {
18
18
  'flat-exports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"spreadArrays", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
19
19
  name: string;
20
20
  };
21
+ 'html-logical-properties': import("eslint").Rule.RuleModule;
21
22
  'injection-token-description': import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalid-injection-token-description", readonly unknown[], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
22
23
  name: string;
23
24
  };
@@ -37,6 +38,9 @@ declare const plugin: {
37
38
  'no-implicit-public': import("@typescript-eslint/utils/ts-eslint").RuleModule<"implicitPublic", readonly unknown[], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
38
39
  name: string;
39
40
  };
41
+ 'no-legacy-peer-deps': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noLegacyPeerDeps", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
42
+ name: string;
43
+ };
40
44
  'no-playwright-empty-fill': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useClear", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
41
45
  name: string;
42
46
  };
package/index.esm.js CHANGED
@@ -66,49 +66,6 @@ var htmlEslint = defineConfig([
66
66
  message: 'Icons must be configured, for example: \n<button tuiIconButton [iconStart]="options.iconStart" [iconEnd]="options.iconEnd" /> \n<tui-icon [icon]="options.icon" />',
67
67
  },
68
68
  ],
69
- 'html/no-restricted-attrs': [
70
- 'error',
71
- {
72
- attrPatterns: [
73
- String.raw `\[style\.left(\.[a-z]+)?\]`,
74
- String.raw `\[style\.right(\.[a-z]+)?\]`,
75
- String.raw `\[style\.top(\.[a-z]+)?\]`,
76
- String.raw `\[style\.bottom(\.[a-z]+)?\]`,
77
- String.raw `\[style\.margin-left(\.[a-z]+)?\]`,
78
- String.raw `\[style\.margin-right(\.[a-z]+)?\]`,
79
- String.raw `\[style\.margin-top(\.[a-z]+)?\]`,
80
- String.raw `\[style\.margin-bottom(\.[a-z]+)?\]`,
81
- String.raw `\[style\.padding-left(\.[a-z]+)?\]`,
82
- String.raw `\[style\.padding-right(\.[a-z]+)?\]`,
83
- String.raw `\[style\.padding-top(\.[a-z]+)?\]`,
84
- String.raw `\[style\.padding-bottom(\.[a-z]+)?\]`,
85
- String.raw `\[style\.border-left(\.[a-z]+)?\]`,
86
- String.raw `\[style\.border-right(\.[a-z]+)?\]`,
87
- String.raw `\[style\.border-top(\.[a-z]+)?\]`,
88
- String.raw `\[style\.border-bottom(\.[a-z]+)?\]`,
89
- ],
90
- message: `
91
- Use logical CSS properties instead of directional properties. Replace:
92
- • left → inset-inline-start
93
- • right → inset-inline-end
94
- • top → inset-block-start
95
- • bottom → inset-block-end
96
- • margin-left → margin-inline-start
97
- • margin-right → margin-inline-end
98
- • margin-top → margin-block-start
99
- • margin-bottom → margin-block-end
100
- • padding-left → padding-inline-start
101
- • padding-right → padding-inline-end
102
- • padding-top → padding-block-start
103
- • padding-bottom → padding-block-end
104
- • border-left → border-inline-start
105
- • border-right → border-inline-end
106
- • border-top → border-block-start
107
- • border-bottom → border-block-end
108
- `,
109
- tagPatterns: ['.*'],
110
- },
111
- ],
112
69
  'html/require-closing-tags': 'off', // prettier conflicts
113
70
  'html/require-img-alt': ['error', { substitute: ['[alt]', '[attr.alt]'] }],
114
71
  'html/use-baseline': 'off',
@@ -213,6 +170,39 @@ const TUI_MEMBER_ORDERING_CONVENTION = [
213
170
  '#private-instance-method',
214
171
  ];
215
172
 
173
+ function buildAST(text) {
174
+ const lines = text.split('\n');
175
+ const lastLine = lines[lines.length - 1] ?? '';
176
+ return {
177
+ body: [],
178
+ comments: [],
179
+ loc: {
180
+ end: { column: lastLine.length, line: lines.length },
181
+ start: { column: 0, line: 1 },
182
+ },
183
+ range: [0, text.length],
184
+ tokens: [],
185
+ type: 'Program',
186
+ };
187
+ }
188
+ function parse(text) {
189
+ return buildAST(text);
190
+ }
191
+ function parseForESLint(text) {
192
+ return {
193
+ ast: buildAST(text),
194
+ scopeManager: null,
195
+ services: {},
196
+ visitorKeys: { Program: [] },
197
+ };
198
+ }
199
+
200
+ var npmrcParser = /*#__PURE__*/Object.freeze({
201
+ __proto__: null,
202
+ parse: parse,
203
+ parseForESLint: parseForESLint
204
+ });
205
+
216
206
  const require$2 = createRequire(import.meta.url);
217
207
  function getAngularVersion() {
218
208
  try {
@@ -989,6 +979,7 @@ var recommended = defineConfig([
989
979
  '@angular-eslint/template/prefer-control-flow': angularVersion >= modernAngularRules.preferControlFlow ? 'error' : 'off',
990
980
  '@angular-eslint/template/prefer-self-closing-tags': 'error',
991
981
  '@angular-eslint/template/prefer-template-literal': angularVersion >= modernAngularRules.templateLiteral ? 'error' : 'off',
982
+ '@taiga-ui/experience-next/html-logical-properties': 'error',
992
983
  '@taiga-ui/experience-next/no-href-with-router-link': 'error',
993
984
  '@taiga-ui/experience-next/no-project-as-in-ng-template': 'error',
994
985
  },
@@ -1162,6 +1153,11 @@ var recommended = defineConfig([
1162
1153
  files: ['**/*.md', '**/*.html', '**/*.cy.ts', '**/*.spec.ts'],
1163
1154
  rules: { 'no-irregular-whitespace': 'off' },
1164
1155
  },
1156
+ {
1157
+ files: ['**/.npmrc'],
1158
+ languageOptions: { parser: npmrcParser },
1159
+ rules: { '@taiga-ui/experience-next/no-legacy-peer-deps': 'error' },
1160
+ },
1165
1161
  ]);
1166
1162
 
1167
1163
  const allPackageJSONs = globSync('**/package.json', {
@@ -1240,7 +1236,7 @@ function readJSON(path) {
1240
1236
  }
1241
1237
  }
1242
1238
 
1243
- const config$3 = {
1239
+ const config$4 = {
1244
1240
  create(context) {
1245
1241
  const classesInFile = new Map();
1246
1242
  return {
@@ -1331,8 +1327,8 @@ function intersect(a, b) {
1331
1327
  return a.some((type) => origin.has(type));
1332
1328
  }
1333
1329
 
1334
- const createRule$e = ESLintUtils.RuleCreator((name) => name);
1335
- var classPropertyNaming = createRule$e({
1330
+ const createRule$f = ESLintUtils.RuleCreator((name) => name);
1331
+ var classPropertyNaming = createRule$f({
1336
1332
  create(context, [configs]) {
1337
1333
  const parserServices = ESLintUtils.getParserServices(context);
1338
1334
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1399,7 +1395,7 @@ var classPropertyNaming = createRule$e({
1399
1395
  name: 'class-property-naming',
1400
1396
  });
1401
1397
 
1402
- const config$2 = {
1398
+ const config$3 = {
1403
1399
  create(context) {
1404
1400
  const order = context.options[0] || {};
1405
1401
  return {
@@ -1501,9 +1497,9 @@ function isExternalPureTuple(typeChecker, type) {
1501
1497
  return typeArgs.every((item) => isClassType(item));
1502
1498
  }
1503
1499
 
1504
- const createRule$d = ESLintUtils.RuleCreator((name) => name);
1505
- const MESSAGE_ID$6 = 'spreadArrays';
1506
- var flatExports = createRule$d({
1500
+ const createRule$e = ESLintUtils.RuleCreator((name) => name);
1501
+ const MESSAGE_ID$7 = 'spreadArrays';
1502
+ var flatExports = createRule$e({
1507
1503
  create(context) {
1508
1504
  const parserServices = ESLintUtils.getParserServices(context);
1509
1505
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1568,7 +1564,7 @@ var flatExports = createRule$d({
1568
1564
  fix(fixer) {
1569
1565
  return fixer.replaceText(elementNode, `...${meta.name}`);
1570
1566
  },
1571
- messageId: MESSAGE_ID$6,
1567
+ messageId: MESSAGE_ID$7,
1572
1568
  node: elementNode,
1573
1569
  });
1574
1570
  }
@@ -1619,7 +1615,7 @@ var flatExports = createRule$d({
1619
1615
  },
1620
1616
  fixable: 'code',
1621
1617
  messages: {
1622
- [MESSAGE_ID$6]: 'Spread "{{ name }}" to avoid nested arrays in exported entities.',
1618
+ [MESSAGE_ID$7]: 'Spread "{{ name }}" to avoid nested arrays in exported entities.',
1623
1619
  },
1624
1620
  schema: [],
1625
1621
  type: 'suggestion',
@@ -1627,10 +1623,90 @@ var flatExports = createRule$d({
1627
1623
  name: 'flat-exports',
1628
1624
  });
1629
1625
 
1626
+ const DIRECTIONAL_TO_LOGICAL = {
1627
+ 'border-bottom': 'border-block-end',
1628
+ 'border-left': 'border-inline-start',
1629
+ 'border-right': 'border-inline-end',
1630
+ 'border-top': 'border-block-start',
1631
+ bottom: 'inset-block-end',
1632
+ left: 'inset-inline-start',
1633
+ 'margin-bottom': 'margin-block-end',
1634
+ 'margin-left': 'margin-inline-start',
1635
+ 'margin-right': 'margin-inline-end',
1636
+ 'margin-top': 'margin-block-start',
1637
+ 'padding-bottom': 'padding-block-end',
1638
+ 'padding-left': 'padding-inline-start',
1639
+ 'padding-right': 'padding-inline-end',
1640
+ 'padding-top': 'padding-block-start',
1641
+ right: 'inset-inline-end',
1642
+ top: 'inset-block-start',
1643
+ };
1644
+ const STYLE_PREFIX = 'style.';
1645
+ const MESSAGE_ID$6 = 'html-logical-properties';
1646
+ const MESSAGE = `
1647
+ Use logical CSS properties instead of directional properties. Replace:
1648
+ • left → inset-inline-start
1649
+ • right → inset-inline-end
1650
+ • top → inset-block-start
1651
+ • bottom → inset-block-end
1652
+ • margin-left → margin-inline-start
1653
+ • margin-right → margin-inline-end
1654
+ • margin-top → margin-block-start
1655
+ • margin-bottom → margin-block-end
1656
+ • padding-left → padding-inline-start
1657
+ • padding-right → padding-inline-end
1658
+ • padding-top → padding-block-start
1659
+ • padding-bottom → padding-block-end
1660
+ • border-left → border-inline-start
1661
+ • border-right → border-inline-end
1662
+ • border-top → border-block-start
1663
+ • border-bottom → border-block-end
1664
+ `;
1665
+ const config$2 = {
1666
+ create(context) {
1667
+ return {
1668
+ BoundAttribute(rawNode) {
1669
+ const node = rawNode;
1670
+ if (!node.keySpan.details?.startsWith(STYLE_PREFIX)) {
1671
+ return;
1672
+ }
1673
+ const logicalProperty = DIRECTIONAL_TO_LOGICAL[node.name];
1674
+ if (!logicalProperty) {
1675
+ return;
1676
+ }
1677
+ const { keySpan } = node;
1678
+ const propertyStart = keySpan.start.offset + STYLE_PREFIX.length;
1679
+ const propertyEnd = propertyStart + node.name.length;
1680
+ context.report({
1681
+ fix: (fixer) => fixer.replaceTextRange([propertyStart, propertyEnd], logicalProperty),
1682
+ loc: {
1683
+ end: {
1684
+ column: keySpan.end.col,
1685
+ line: keySpan.end.line + 1,
1686
+ },
1687
+ start: {
1688
+ column: keySpan.start.col,
1689
+ line: keySpan.start.line + 1,
1690
+ },
1691
+ },
1692
+ messageId: MESSAGE_ID$6,
1693
+ });
1694
+ },
1695
+ };
1696
+ },
1697
+ meta: {
1698
+ docs: { description: MESSAGE },
1699
+ fixable: 'code',
1700
+ messages: { [MESSAGE_ID$6]: MESSAGE },
1701
+ schema: [],
1702
+ type: 'suggestion',
1703
+ },
1704
+ };
1705
+
1630
1706
  const MESSAGE_ID$5 = 'invalid-injection-token-description';
1631
1707
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
1632
- const createRule$c = ESLintUtils.RuleCreator((name) => name);
1633
- const rule$9 = createRule$c({
1708
+ const createRule$d = ESLintUtils.RuleCreator((name) => name);
1709
+ const rule$a = createRule$d({
1634
1710
  create(context) {
1635
1711
  return {
1636
1712
  'NewExpression[callee.name="InjectionToken"]'(node) {
@@ -1697,8 +1773,8 @@ const DEFAULT_OPTIONS = {
1697
1773
  importDeclaration: '^@taiga-ui*',
1698
1774
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
1699
1775
  };
1700
- const createRule$b = ESLintUtils.RuleCreator((name) => name);
1701
- const rule$8 = createRule$b({
1776
+ const createRule$c = ESLintUtils.RuleCreator((name) => name);
1777
+ const rule$9 = createRule$c({
1702
1778
  create(context) {
1703
1779
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
1704
1780
  const hasNonCodeExtension = (source) => {
@@ -1785,13 +1861,13 @@ const rule$8 = createRule$b({
1785
1861
  name: 'no-deep-imports',
1786
1862
  });
1787
1863
 
1788
- const createRule$a = ESLintUtils.RuleCreator((name) => name);
1864
+ const createRule$b = ESLintUtils.RuleCreator((name) => name);
1789
1865
  const resolveCacheByOptions = new WeakMap();
1790
1866
  const nearestFileUpCache = new Map();
1791
1867
  const markerCache = new Map();
1792
1868
  const indexFileCache = new Map();
1793
1869
  const indexExportsCache = new Map();
1794
- var noDeepImportsToIndexedPackages = createRule$a({
1870
+ var noDeepImportsToIndexedPackages = createRule$b({
1795
1871
  create(context) {
1796
1872
  const parserServices = ESLintUtils.getParserServices(context);
1797
1873
  const program = parserServices.program;
@@ -2031,8 +2107,8 @@ const config$1 = {
2031
2107
  },
2032
2108
  };
2033
2109
 
2034
- const createRule$9 = ESLintUtils.RuleCreator((name) => name);
2035
- const rule$7 = createRule$9({
2110
+ const createRule$a = ESLintUtils.RuleCreator((name) => name);
2111
+ const rule$8 = createRule$a({
2036
2112
  create(context) {
2037
2113
  const checkImplicitPublic = (node) => {
2038
2114
  const classRef = getClass(node);
@@ -2103,6 +2179,46 @@ function getClass(node) {
2103
2179
  return getClass(node.parent);
2104
2180
  }
2105
2181
 
2182
+ const createRule$9 = ESLintUtils.RuleCreator((name) => name);
2183
+ const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
2184
+ const rule$7 = createRule$9({
2185
+ create(context) {
2186
+ return {
2187
+ Program(node) {
2188
+ const text = context.sourceCode.getText(node);
2189
+ const lines = text.split('\n');
2190
+ for (const [index, line] of lines.entries()) {
2191
+ const trimmed = line.trim();
2192
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';')) {
2193
+ continue;
2194
+ }
2195
+ if (LEGACY_PEER_DEPS_PATTERN.test(trimmed)) {
2196
+ const startColumn = line.search(/\S/);
2197
+ context.report({
2198
+ loc: {
2199
+ end: { column: line.length, line: index + 1 },
2200
+ start: { column: startColumn, line: index + 1 },
2201
+ },
2202
+ messageId: 'noLegacyPeerDeps',
2203
+ });
2204
+ }
2205
+ }
2206
+ },
2207
+ };
2208
+ },
2209
+ meta: {
2210
+ docs: {
2211
+ description: 'Disallow legacy-peer-deps=true in .npmrc. Fix peer dependency conflicts instead of bypassing them.',
2212
+ },
2213
+ messages: {
2214
+ noLegacyPeerDeps: 'Do not use "legacy-peer-deps=true" in .npmrc. Fix peer dependency conflicts instead of bypassing the resolver.',
2215
+ },
2216
+ schema: [],
2217
+ type: 'problem',
2218
+ },
2219
+ name: 'no-legacy-peer-deps',
2220
+ });
2221
+
2106
2222
  const createRule$8 = ESLintUtils.RuleCreator((name) => name);
2107
2223
  const rule$6 = createRule$8({
2108
2224
  create(context) {
@@ -3748,15 +3864,17 @@ const plugin = {
3748
3864
  version: pkg.version,
3749
3865
  },
3750
3866
  rules: {
3751
- 'array-as-const': config$3,
3867
+ 'array-as-const': config$4,
3752
3868
  'class-property-naming': classPropertyNaming,
3753
- 'decorator-key-sort': config$2,
3869
+ 'decorator-key-sort': config$3,
3754
3870
  'flat-exports': flatExports,
3755
- 'injection-token-description': rule$9,
3756
- 'no-deep-imports': rule$8,
3871
+ 'html-logical-properties': config$2,
3872
+ 'injection-token-description': rule$a,
3873
+ 'no-deep-imports': rule$9,
3757
3874
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
3758
3875
  'no-href-with-router-link': config$1,
3759
- 'no-implicit-public': rule$7,
3876
+ 'no-implicit-public': rule$8,
3877
+ 'no-legacy-peer-deps': rule$7,
3760
3878
  'no-playwright-empty-fill': rule$6,
3761
3879
  'no-project-as-in-ng-template': config,
3762
3880
  'no-redundant-type-annotation': rule$5,
@@ -3779,6 +3897,7 @@ Object.assign(plugin.configs, {
3779
3897
  recommended: [
3780
3898
  { files: ALL_TS_JS_FILES, plugins: { '@taiga-ui/experience-next': plugin } },
3781
3899
  { files: ['**/*.html'], plugins: { '@taiga-ui/experience-next': plugin } },
3900
+ { files: ['**/.npmrc'], plugins: { '@taiga-ui/experience-next': plugin } },
3782
3901
  ...recommended,
3783
3902
  ],
3784
3903
  ['taiga-specific']: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.464.0",
3
+ "version": "0.466.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,3 @@
1
+ import { type Rule } from 'eslint';
2
+ declare const config: Rule.RuleModule;
3
+ export default config;
@@ -0,0 +1,5 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const rule: ESLintUtils.RuleModule<"noLegacyPeerDeps", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default rule;
@@ -0,0 +1,25 @@
1
+ interface NpmrcAST {
2
+ type: 'Program';
3
+ body: never[];
4
+ comments: never[];
5
+ tokens: never[];
6
+ range: [number, number];
7
+ loc: {
8
+ start: {
9
+ line: number;
10
+ column: number;
11
+ };
12
+ end: {
13
+ line: number;
14
+ column: number;
15
+ };
16
+ };
17
+ }
18
+ export declare function parse(text: string): NpmrcAST;
19
+ export declare function parseForESLint(text: string): {
20
+ ast: NpmrcAST;
21
+ visitorKeys: Record<string, string[]>;
22
+ scopeManager: null;
23
+ services: Record<string, never>;
24
+ };
25
+ export {};