@taiga-ui/eslint-plugin-experience-next 0.451.0 → 0.453.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 +44 -1
- package/index.d.ts +1 -1
- package/index.esm.js +132 -74
- package/package.json +3 -3
- package/rules/no-string-literal-concat.d.ts +1 -1
package/README.md
CHANGED
|
@@ -323,8 +323,51 @@ const c = a + b;
|
|
|
323
323
|
const c = `${a}${b}`;
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
+
When the concatenation is a **direct expression inside a template literal**, the parts are inlined into the outer
|
|
327
|
+
template instead of producing a nested template literal:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
// ❌ error
|
|
331
|
+
const url = `${base}${path + query}`;
|
|
332
|
+
|
|
333
|
+
// ✅ after autofix — inlined, no nesting
|
|
334
|
+
const url = `${base}${path}${query}`;
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
// ❌ error — literal concat inside template
|
|
339
|
+
const mask = `${'HH' + ':MM'}`;
|
|
340
|
+
|
|
341
|
+
// ✅ after autofix
|
|
342
|
+
const mask = `HH:MM`;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
When the concatenation appears **inside a method call or other expression** within a template literal, the rule skips it
|
|
346
|
+
to avoid creating unreadable nested template literals like `` `${`${a}${b}`.method()}` ``.
|
|
347
|
+
|
|
348
|
+
The rule also **flattens already-nested template literals** produced by earlier autofixes or written by hand:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
// ❌ error
|
|
352
|
+
const s = `${`${dateMode}${dateTimeSeparator}`}HH:MM`;
|
|
353
|
+
|
|
354
|
+
// ✅ after autofix
|
|
355
|
+
const s = `${dateMode}${dateTimeSeparator}HH:MM`;
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Concatenation that uses **inline comments between parts** is intentionally left untouched, as the comments serve as
|
|
359
|
+
documentation:
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
// ✅ not flagged — comments are preserved
|
|
363
|
+
const urlRegex =
|
|
364
|
+
String.raw`^([a-zA-Z]+:\/\/)?` + // protocol
|
|
365
|
+
String.raw`([\w-]+\.)+[\w]{2,}` + // domain
|
|
366
|
+
String.raw`(\/\S*)?$`; // path
|
|
367
|
+
```
|
|
368
|
+
|
|
326
369
|
> For mixed concatenation (`'prefix' + variable`) use the standard `prefer-template` rule, which is already enabled in
|
|
327
|
-
> `recommended`. Template literals (`` `foo` + `bar` ``) are not flagged by this rule.
|
|
370
|
+
> `recommended`. Template literals (`` `foo` + `bar` ``) and tagged templates are not flagged by this rule.
|
|
328
371
|
|
|
329
372
|
---
|
|
330
373
|
|
package/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ declare const plugin: {
|
|
|
40
40
|
'no-playwright-empty-fill': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useClear", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
41
41
|
name: string;
|
|
42
42
|
};
|
|
43
|
-
'no-string-literal-concat': import("@typescript-eslint/utils/ts-eslint").RuleModule<"mergeLiterals" | "useTemplate", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
43
|
+
'no-string-literal-concat': import("@typescript-eslint/utils/ts-eslint").RuleModule<"flattenTemplate" | "mergeLiterals" | "useTemplate", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
44
44
|
name: string;
|
|
45
45
|
};
|
|
46
46
|
'object-single-line': import("@typescript-eslint/utils/ts-eslint").RuleModule<"oneLine", [{
|
package/index.esm.js
CHANGED
|
@@ -418,7 +418,7 @@ var recommended = defineConfig([
|
|
|
418
418
|
'@typescript-eslint/no-base-to-string': 'off',
|
|
419
419
|
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
|
|
420
420
|
'@typescript-eslint/no-confusing-void-expression': 'off',
|
|
421
|
-
'@typescript-eslint/no-deprecated': 'off',
|
|
421
|
+
'@typescript-eslint/no-deprecated': 'off', // TODO: should be error?
|
|
422
422
|
'@typescript-eslint/no-duplicate-enum-values': 'error',
|
|
423
423
|
'@typescript-eslint/no-duplicate-type-constituents': 'error',
|
|
424
424
|
'@typescript-eslint/no-empty-function': [
|
|
@@ -435,7 +435,7 @@ var recommended = defineConfig([
|
|
|
435
435
|
},
|
|
436
436
|
],
|
|
437
437
|
'@typescript-eslint/no-empty-object-type': 'error',
|
|
438
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
438
|
+
'@typescript-eslint/no-explicit-any': 'off', // TODO: should be error?
|
|
439
439
|
'@typescript-eslint/no-extra-non-null-assertion': 'error',
|
|
440
440
|
'@typescript-eslint/no-extraneous-class': [
|
|
441
441
|
'error',
|
|
@@ -446,14 +446,15 @@ var recommended = defineConfig([
|
|
|
446
446
|
allowWithDecorator: true,
|
|
447
447
|
},
|
|
448
448
|
],
|
|
449
|
-
'@typescript-eslint/no-floating-promises': 'off',
|
|
449
|
+
'@typescript-eslint/no-floating-promises': 'off', // TODO: should be error, just void before Promise
|
|
450
450
|
'@typescript-eslint/no-for-in-array': 'error',
|
|
451
451
|
'@typescript-eslint/no-implied-eval': 'error',
|
|
452
452
|
'@typescript-eslint/no-import-type-side-effects': 'off', // verbatimModuleSyntax should be false
|
|
453
453
|
'@typescript-eslint/no-inferrable-types': 'error',
|
|
454
454
|
'@typescript-eslint/no-invalid-void-type': 'off',
|
|
455
|
+
'@typescript-eslint/no-loop-func': 'off', // TODO: should be error?
|
|
455
456
|
'@typescript-eslint/no-magic-numbers': 'off',
|
|
456
|
-
'@typescript-eslint/no-misused-promises': 'off',
|
|
457
|
+
'@typescript-eslint/no-misused-promises': 'off', // TODO: ['error', {checksVoidReturn: {attributes: false}}],
|
|
457
458
|
'@typescript-eslint/no-misused-spread': 'off',
|
|
458
459
|
'@typescript-eslint/no-namespace': ['error', { allowDeclarations: true }],
|
|
459
460
|
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
|
|
@@ -491,22 +492,22 @@ var recommended = defineConfig([
|
|
|
491
492
|
},
|
|
492
493
|
},
|
|
493
494
|
],
|
|
494
|
-
'@typescript-eslint/no-shadow': 'off',
|
|
495
|
+
'@typescript-eslint/no-shadow': 'off', // TODO: should be error?
|
|
495
496
|
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
|
|
496
497
|
'@typescript-eslint/no-unnecessary-condition': 'error',
|
|
497
498
|
'@typescript-eslint/no-unnecessary-qualifier': 'error',
|
|
498
|
-
'@typescript-eslint/no-unnecessary-template-expression': '
|
|
499
|
+
'@typescript-eslint/no-unnecessary-template-expression': 'error',
|
|
499
500
|
'@typescript-eslint/no-unnecessary-type-arguments': 'error',
|
|
500
501
|
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
|
501
502
|
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
|
|
502
|
-
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
|
|
503
|
-
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
504
|
-
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
505
|
-
'@typescript-eslint/no-unsafe-call': 'off',
|
|
503
|
+
'@typescript-eslint/no-unnecessary-type-parameters': 'off', // TODO: should be error?
|
|
504
|
+
'@typescript-eslint/no-unsafe-argument': 'off', // TODO: should be error?
|
|
505
|
+
'@typescript-eslint/no-unsafe-assignment': 'off', // TODO: should be error?
|
|
506
|
+
'@typescript-eslint/no-unsafe-call': 'off', // TODO: should be error?
|
|
506
507
|
'@typescript-eslint/no-unsafe-declaration-merging': 'error',
|
|
507
|
-
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
508
|
-
'@typescript-eslint/no-unsafe-return': 'off',
|
|
509
|
-
'@typescript-eslint/no-unsafe-type-assertion': 'off',
|
|
508
|
+
'@typescript-eslint/no-unsafe-member-access': 'off', // TODO: should be error?
|
|
509
|
+
'@typescript-eslint/no-unsafe-return': 'off', // TODO: should be error?
|
|
510
|
+
'@typescript-eslint/no-unsafe-type-assertion': 'off', // TODO: should be error?
|
|
510
511
|
'@typescript-eslint/no-unused-expressions': [
|
|
511
512
|
'error',
|
|
512
513
|
{
|
|
@@ -540,7 +541,7 @@ var recommended = defineConfig([
|
|
|
540
541
|
'@typescript-eslint/prefer-find': 'error',
|
|
541
542
|
'@typescript-eslint/prefer-for-of': 'error',
|
|
542
543
|
'@typescript-eslint/prefer-includes': 'error',
|
|
543
|
-
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
|
544
|
+
'@typescript-eslint/prefer-nullish-coalescing': 'off', // TODO: ['error', {ignorePrimitives: {boolean: true, number: true, string: true}}]
|
|
544
545
|
'@typescript-eslint/prefer-optional-chain': 'error',
|
|
545
546
|
'@typescript-eslint/prefer-readonly': ['error'],
|
|
546
547
|
'@typescript-eslint/prefer-readonly-parameter-types': 'off',
|
|
@@ -559,9 +560,9 @@ var recommended = defineConfig([
|
|
|
559
560
|
'@typescript-eslint/require-array-sort-compare': 'error',
|
|
560
561
|
'@typescript-eslint/require-await': 'error',
|
|
561
562
|
'@typescript-eslint/restrict-plus-operands': 'error',
|
|
562
|
-
'@typescript-eslint/restrict-template-expressions': 'off',
|
|
563
|
+
'@typescript-eslint/restrict-template-expressions': 'off', // TODO: should be error?
|
|
563
564
|
'@typescript-eslint/sort-type-constituents': 'error',
|
|
564
|
-
'@typescript-eslint/strict-boolean-expressions': 'off',
|
|
565
|
+
'@typescript-eslint/strict-boolean-expressions': 'off', // TODO: should be error?
|
|
565
566
|
'@typescript-eslint/switch-exhaustiveness-check': [
|
|
566
567
|
'error',
|
|
567
568
|
{
|
|
@@ -601,6 +602,7 @@ var recommended = defineConfig([
|
|
|
601
602
|
'import/no-absolute-path': 'error',
|
|
602
603
|
'import/no-cycle': 'error',
|
|
603
604
|
'import/no-duplicates': ['error', { 'prefer-inline': true }],
|
|
605
|
+
'import/no-extraneous-dependencies': 'off',
|
|
604
606
|
'import/no-mutable-exports': 'error',
|
|
605
607
|
'import/no-named-as-default': 'error',
|
|
606
608
|
'import/no-self-import': 'error',
|
|
@@ -643,7 +645,7 @@ var recommended = defineConfig([
|
|
|
643
645
|
skipTemplates: false,
|
|
644
646
|
},
|
|
645
647
|
],
|
|
646
|
-
'no-loop-func': '
|
|
648
|
+
'no-loop-func': 'off',
|
|
647
649
|
'no-nested-ternary': 'error',
|
|
648
650
|
'no-prototype-builtins': 'off',
|
|
649
651
|
'no-restricted-imports': [
|
|
@@ -708,6 +710,7 @@ var recommended = defineConfig([
|
|
|
708
710
|
},
|
|
709
711
|
],
|
|
710
712
|
'no-return-assign': ['error', 'always'],
|
|
713
|
+
'no-shadow': 'off',
|
|
711
714
|
'no-unneeded-ternary': 'error',
|
|
712
715
|
'no-useless-escape': 'error',
|
|
713
716
|
'no-useless-rename': [
|
|
@@ -765,7 +768,8 @@ var recommended = defineConfig([
|
|
|
765
768
|
'promise/always-return': 'off',
|
|
766
769
|
'promise/catch-or-return': 'off',
|
|
767
770
|
'promise/no-callback-in-promise': 'off',
|
|
768
|
-
'promise/no-nesting': 'off',
|
|
771
|
+
'promise/no-nesting': 'off', // TODO: should be error?
|
|
772
|
+
'promise/no-return-in-finally': 'error',
|
|
769
773
|
'promise/param-names': 'error',
|
|
770
774
|
'regexp/no-unused-capturing-group': [
|
|
771
775
|
'error',
|
|
@@ -776,6 +780,8 @@ var recommended = defineConfig([
|
|
|
776
780
|
],
|
|
777
781
|
'simple-import-sort/exports': 'error',
|
|
778
782
|
'simple-import-sort/imports': 'error',
|
|
783
|
+
'sonarjs/cognitive-complexity': 'off', // TODO: should be error? ['error', 15]
|
|
784
|
+
'sonarjs/no-duplicate-string': 'off', // TODO: should be error? ['error', {threshold: 5}]
|
|
779
785
|
'sonarjs/no-identical-functions': 'error',
|
|
780
786
|
'sonarjs/no-inverted-boolean-check': 'error',
|
|
781
787
|
'spaced-comment': ['error', 'always', { markers: ['/'] }],
|
|
@@ -784,18 +790,25 @@ var recommended = defineConfig([
|
|
|
784
790
|
'unicorn/escape-case': 'error',
|
|
785
791
|
'unicorn/filename-case': ['error', { case: 'kebabCase' }],
|
|
786
792
|
'unicorn/new-for-builtins': 'error',
|
|
793
|
+
'unicorn/no-array-for-each': 'off', // TODO: should be error
|
|
787
794
|
'unicorn/no-array-method-this-argument': 'error',
|
|
788
795
|
'unicorn/no-array-push-push': 'error',
|
|
789
796
|
'unicorn/no-await-in-promise-methods': 'error',
|
|
790
797
|
'unicorn/no-empty-file': 'error',
|
|
791
798
|
'unicorn/no-magic-array-flat-depth': 'error',
|
|
799
|
+
'unicorn/no-negated-condition': 'error',
|
|
792
800
|
'unicorn/no-negation-in-equality-check': 'error',
|
|
793
801
|
'unicorn/no-new-array': 'error',
|
|
802
|
+
'unicorn/no-object-as-default-parameter': 'off',
|
|
794
803
|
'unicorn/no-single-promise-in-promise-methods': 'error',
|
|
795
804
|
'unicorn/no-typeof-undefined': 'error',
|
|
796
805
|
'unicorn/no-unnecessary-polyfills': 'error',
|
|
797
806
|
'unicorn/no-useless-spread': 'error',
|
|
807
|
+
'unicorn/no-useless-undefined': 'error',
|
|
808
|
+
'unicorn/prefer-dom-node-append': 'off', // TODO: should be error?
|
|
798
809
|
'unicorn/prefer-logical-operator-over-ternary': 'error',
|
|
810
|
+
'unicorn/prefer-number-properties': 'error',
|
|
811
|
+
'unicorn/prefer-prototype-methods': 'error',
|
|
799
812
|
'unicorn/prefer-query-selector': 'error',
|
|
800
813
|
'unicorn/prefer-set-size': 'error',
|
|
801
814
|
'unicorn/prefer-string-raw': 'error',
|
|
@@ -1021,7 +1034,6 @@ var recommended = defineConfig([
|
|
|
1021
1034
|
extends: [jest.configs['flat/recommended']],
|
|
1022
1035
|
rules: {
|
|
1023
1036
|
'@typescript-eslint/no-extraneous-class': 'off',
|
|
1024
|
-
'@typescript-eslint/no-shadow': 'off',
|
|
1025
1037
|
'compat/compat': 'off',
|
|
1026
1038
|
'jest/expect-expect': 'off',
|
|
1027
1039
|
'jest/max-expects': 'off',
|
|
@@ -1078,7 +1090,7 @@ var recommended = defineConfig([
|
|
|
1078
1090
|
{ maxNumberOfTopLevelDescribes: 1 },
|
|
1079
1091
|
],
|
|
1080
1092
|
'jest/unbound-method': 'off',
|
|
1081
|
-
'jest/valid-title': '
|
|
1093
|
+
'jest/valid-title': 'off',
|
|
1082
1094
|
},
|
|
1083
1095
|
},
|
|
1084
1096
|
{
|
|
@@ -1301,7 +1313,7 @@ function getFieldTypes(type, checker) {
|
|
|
1301
1313
|
typeNames.push('string');
|
|
1302
1314
|
}
|
|
1303
1315
|
else {
|
|
1304
|
-
const symbol = type.getSymbol()
|
|
1316
|
+
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
1305
1317
|
if (symbol) {
|
|
1306
1318
|
typeNames.push(symbol.getName());
|
|
1307
1319
|
}
|
|
@@ -1391,7 +1403,7 @@ const config$1 = {
|
|
|
1391
1403
|
return {
|
|
1392
1404
|
ClassDeclaration(node) {
|
|
1393
1405
|
const decorators = Array.from(node.decorators ?? []);
|
|
1394
|
-
|
|
1406
|
+
for (const decorator of decorators) {
|
|
1395
1407
|
const { expression } = decorator;
|
|
1396
1408
|
const decoratorName = expression.callee?.name ?? '';
|
|
1397
1409
|
if (decoratorName in (order || {})) {
|
|
@@ -1423,7 +1435,7 @@ const config$1 = {
|
|
|
1423
1435
|
}
|
|
1424
1436
|
}
|
|
1425
1437
|
}
|
|
1426
|
-
}
|
|
1438
|
+
}
|
|
1427
1439
|
},
|
|
1428
1440
|
};
|
|
1429
1441
|
},
|
|
@@ -1534,20 +1546,20 @@ var flatExports = createRule$c({
|
|
|
1534
1546
|
if (!isPureArray(arr)) {
|
|
1535
1547
|
continue;
|
|
1536
1548
|
}
|
|
1537
|
-
arr.elements.
|
|
1549
|
+
for (const [index, meta] of arr.elements.entries()) {
|
|
1538
1550
|
if (!meta.isArrayLike) {
|
|
1539
|
-
|
|
1551
|
+
continue;
|
|
1540
1552
|
}
|
|
1541
1553
|
const elementNode = arr.node.elements[index];
|
|
1542
1554
|
if (elementNode?.type !== AST_NODE_TYPES.Identifier) {
|
|
1543
|
-
|
|
1555
|
+
continue;
|
|
1544
1556
|
}
|
|
1545
1557
|
const hasLocalArrayMeta = arrays.has(meta.name);
|
|
1546
|
-
const isExternalPure =
|
|
1547
|
-
?
|
|
1548
|
-
:
|
|
1558
|
+
const isExternalPure = hasLocalArrayMeta
|
|
1559
|
+
? false
|
|
1560
|
+
: isExternalPureTuple(typeChecker, meta.type);
|
|
1549
1561
|
if (!hasLocalArrayMeta && !isExternalPure) {
|
|
1550
|
-
|
|
1562
|
+
continue;
|
|
1551
1563
|
}
|
|
1552
1564
|
context.report({
|
|
1553
1565
|
data: { name: meta.name },
|
|
@@ -1557,7 +1569,7 @@ var flatExports = createRule$c({
|
|
|
1557
1569
|
messageId: MESSAGE_ID$5,
|
|
1558
1570
|
node: elementNode,
|
|
1559
1571
|
});
|
|
1560
|
-
}
|
|
1572
|
+
}
|
|
1561
1573
|
}
|
|
1562
1574
|
},
|
|
1563
1575
|
VariableDeclarator(node) {
|
|
@@ -1622,7 +1634,7 @@ const rule$8 = createRule$b({
|
|
|
1622
1634
|
'NewExpression[callee.name="InjectionToken"]'(node) {
|
|
1623
1635
|
let token;
|
|
1624
1636
|
let name;
|
|
1625
|
-
const [description] = node?.arguments
|
|
1637
|
+
const [description] = node?.arguments ?? [];
|
|
1626
1638
|
if (!description) {
|
|
1627
1639
|
return;
|
|
1628
1640
|
}
|
|
@@ -1701,8 +1713,8 @@ const rule$7 = createRule$a({
|
|
|
1701
1713
|
const filePath = path
|
|
1702
1714
|
.relative(context.cwd, context.filename)
|
|
1703
1715
|
.replaceAll(/\\+/g, '/');
|
|
1704
|
-
const [currentFileProjectName] = (currentProject && new RegExp(currentProject, 'g').exec(filePath))
|
|
1705
|
-
const [importSourceProjectName] = source?.match(new RegExp(projectName, 'g'))
|
|
1716
|
+
const [currentFileProjectName] = (currentProject && new RegExp(currentProject, 'g').exec(filePath)) ?? [];
|
|
1717
|
+
const [importSourceProjectName] = source?.match(new RegExp(projectName, 'g')) ?? [];
|
|
1706
1718
|
return Boolean(currentFileProjectName &&
|
|
1707
1719
|
importSourceProjectName &&
|
|
1708
1720
|
currentFileProjectName === importSourceProjectName);
|
|
@@ -1986,7 +1998,7 @@ const config = {
|
|
|
1986
1998
|
let routerLinkAttribute = null;
|
|
1987
1999
|
let hasRouterLink = false;
|
|
1988
2000
|
let hasHref = false;
|
|
1989
|
-
for (const attr of htmlNode.attributes
|
|
2001
|
+
for (const attr of htmlNode.attributes ?? []) {
|
|
1990
2002
|
const attrName = attr.key.value;
|
|
1991
2003
|
if (attrName?.toLowerCase() === 'href') {
|
|
1992
2004
|
hasHref = true;
|
|
@@ -2003,8 +2015,8 @@ const config = {
|
|
|
2003
2015
|
? fixer.removeRange(hrefAttribute.range)
|
|
2004
2016
|
: null,
|
|
2005
2017
|
messageId: MESSAGE_ID$2,
|
|
2006
|
-
node: (routerLinkAttribute
|
|
2007
|
-
hrefAttribute
|
|
2018
|
+
node: (routerLinkAttribute ??
|
|
2019
|
+
hrefAttribute ??
|
|
2008
2020
|
htmlNode),
|
|
2009
2021
|
});
|
|
2010
2022
|
}
|
|
@@ -2180,7 +2192,7 @@ function isPlaywrightLocatorType(type) {
|
|
|
2180
2192
|
}
|
|
2181
2193
|
|
|
2182
2194
|
const createRule$6 = ESLintUtils.RuleCreator((name) => name);
|
|
2183
|
-
function
|
|
2195
|
+
function isStringLiteral(node) {
|
|
2184
2196
|
return (node.type === AST_NODE_TYPES.Literal &&
|
|
2185
2197
|
typeof node.value === 'string');
|
|
2186
2198
|
}
|
|
@@ -2212,19 +2224,36 @@ function buildMergedString(parts) {
|
|
|
2212
2224
|
.replaceAll(new RegExp(quote, 'g'), `\\${quote}`);
|
|
2213
2225
|
return `${quote}${escaped}${quote}`;
|
|
2214
2226
|
}
|
|
2215
|
-
function
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2227
|
+
function escapeForTemplateLiteral(value) {
|
|
2228
|
+
return value.replaceAll('\\', '\\\\').replaceAll('`', '\\`').replaceAll('${', '\\${');
|
|
2229
|
+
}
|
|
2230
|
+
function partsToTemplateContent(parts, getText) {
|
|
2231
|
+
return parts
|
|
2232
|
+
.map((part) => isStringLiteral(part)
|
|
2233
|
+
? escapeForTemplateLiteral(part.value)
|
|
2234
|
+
: `\${${getText(part)}}`)
|
|
2235
|
+
.join('');
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Returns the raw content between the backticks of a TemplateLiteral,
|
|
2239
|
+
* delegating each expression slot to `renderExpr`.
|
|
2240
|
+
*/
|
|
2241
|
+
function templateContent(template, renderExpr) {
|
|
2242
|
+
return template.quasis
|
|
2243
|
+
.map((quasi, i) => `${quasi.value.raw}${i < template.expressions.length
|
|
2244
|
+
? renderExpr(template.expressions[i], i)
|
|
2245
|
+
: ''}`)
|
|
2226
2246
|
.join('');
|
|
2227
|
-
|
|
2247
|
+
}
|
|
2248
|
+
function hasTemplateLiteralAncestor(node) {
|
|
2249
|
+
let current = node.parent;
|
|
2250
|
+
while (current != null) {
|
|
2251
|
+
if (current.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
2252
|
+
return true;
|
|
2253
|
+
}
|
|
2254
|
+
current = current.parent;
|
|
2255
|
+
}
|
|
2256
|
+
return false;
|
|
2228
2257
|
}
|
|
2229
2258
|
const rule$4 = createRule$6({
|
|
2230
2259
|
create(context) {
|
|
@@ -2239,45 +2268,73 @@ const rule$4 = createRule$6({
|
|
|
2239
2268
|
// Type checking not available — only literal concatenation will be checked
|
|
2240
2269
|
}
|
|
2241
2270
|
function isStringNode(node) {
|
|
2242
|
-
if (
|
|
2271
|
+
if (isStringLiteral(node)) {
|
|
2243
2272
|
return true;
|
|
2244
2273
|
}
|
|
2245
2274
|
if (!parserServices || !checker) {
|
|
2246
2275
|
return false;
|
|
2247
2276
|
}
|
|
2248
2277
|
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
2249
|
-
|
|
2250
|
-
return isStringType(type, checker);
|
|
2278
|
+
return isStringType(checker.getTypeAtLocation(tsNode), checker);
|
|
2251
2279
|
}
|
|
2280
|
+
const getText = (n) => sourceCode.getText(n);
|
|
2281
|
+
const wrapExpr = (expr) => `\${${getText(expr)}}`;
|
|
2252
2282
|
return {
|
|
2253
2283
|
BinaryExpression(node) {
|
|
2254
|
-
if (node.operator !== '+') {
|
|
2284
|
+
if (node.operator !== '+' || !isRootConcat(node)) {
|
|
2255
2285
|
return;
|
|
2256
2286
|
}
|
|
2257
|
-
|
|
2287
|
+
// Comments between parts serve as inline documentation — preserve them
|
|
2288
|
+
if (sourceCode.getCommentsInside(node).length > 0) {
|
|
2258
2289
|
return;
|
|
2259
2290
|
}
|
|
2260
2291
|
const parts = collectParts(node);
|
|
2261
|
-
|
|
2262
|
-
if (allLiterals) {
|
|
2263
|
-
context.report({
|
|
2264
|
-
fix(fixer) {
|
|
2265
|
-
return fixer.replaceText(node, buildMergedString(parts));
|
|
2266
|
-
},
|
|
2267
|
-
messageId: 'mergeLiterals',
|
|
2268
|
-
node,
|
|
2269
|
-
});
|
|
2292
|
+
if (!parts.every((p) => p.type !== AST_NODE_TYPES.TemplateLiteral && isStringNode(p))) {
|
|
2270
2293
|
return;
|
|
2271
2294
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2295
|
+
const allLiterals = parts.every(isStringLiteral);
|
|
2296
|
+
// Direct child of a template expression → inline parts to avoid
|
|
2297
|
+
// nested template literals like `${`${a}${b}`}`
|
|
2298
|
+
if (node.parent.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
2299
|
+
const template = node.parent;
|
|
2300
|
+
// Tagged templates: changing quasis/expressions count alters behaviour
|
|
2301
|
+
if (template.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2274
2304
|
context.report({
|
|
2275
|
-
fix(fixer) {
|
|
2276
|
-
|
|
2277
|
-
},
|
|
2278
|
-
messageId: 'useTemplate',
|
|
2305
|
+
fix: (fixer) => fixer.replaceText(template, `\`${templateContent(template, (expr) => (expr === node ? partsToTemplateContent(parts, getText) : wrapExpr(expr)))}\``),
|
|
2306
|
+
messageId: allLiterals ? 'mergeLiterals' : 'useTemplate',
|
|
2279
2307
|
node,
|
|
2280
2308
|
});
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
// Nested inside a template but not direct child — would produce
|
|
2312
|
+
// `${`${a}${b}`.method()}`, so skip
|
|
2313
|
+
if (hasTemplateLiteralAncestor(node)) {
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
context.report({
|
|
2317
|
+
fix: (fixer) => fixer.replaceText(node, allLiterals
|
|
2318
|
+
? buildMergedString(parts)
|
|
2319
|
+
: `\`${partsToTemplateContent(parts, getText)}\``),
|
|
2320
|
+
messageId: allLiterals ? 'mergeLiterals' : 'useTemplate',
|
|
2321
|
+
node,
|
|
2322
|
+
});
|
|
2323
|
+
},
|
|
2324
|
+
TemplateLiteral(node) {
|
|
2325
|
+
// Tagged templates: changing quasis/expressions count alters behaviour
|
|
2326
|
+
if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
for (const [i, expr] of node.expressions.entries()) {
|
|
2330
|
+
if (expr.type === AST_NODE_TYPES.TemplateLiteral &&
|
|
2331
|
+
expr.parent.type !== AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
2332
|
+
context.report({
|
|
2333
|
+
fix: (fixer) => fixer.replaceText(node, `\`${templateContent(node, (e, j) => (j === i ? templateContent(expr, wrapExpr) : wrapExpr(e)))}\``),
|
|
2334
|
+
messageId: 'flattenTemplate',
|
|
2335
|
+
node: expr,
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2281
2338
|
}
|
|
2282
2339
|
},
|
|
2283
2340
|
};
|
|
@@ -2288,6 +2345,7 @@ const rule$4 = createRule$6({
|
|
|
2288
2345
|
},
|
|
2289
2346
|
fixable: 'code',
|
|
2290
2347
|
messages: {
|
|
2348
|
+
flattenTemplate: 'Flatten nested template literal into its parent.',
|
|
2291
2349
|
mergeLiterals: 'Merge string literals instead of concatenating them.',
|
|
2292
2350
|
useTemplate: 'Use a template literal instead of string concatenation.',
|
|
2293
2351
|
},
|
|
@@ -2336,7 +2394,7 @@ const rule$3 = createRule$5({
|
|
|
2336
2394
|
return false;
|
|
2337
2395
|
}
|
|
2338
2396
|
const [onlyProperty] = node.properties;
|
|
2339
|
-
return
|
|
2397
|
+
return onlyProperty ? !isForbiddenProperty(onlyProperty) : false;
|
|
2340
2398
|
};
|
|
2341
2399
|
const unwrapExpression = (expression) => {
|
|
2342
2400
|
let current = expression;
|
|
@@ -2975,7 +3033,7 @@ const rule$2 = createRule$3({
|
|
|
2975
3033
|
j++;
|
|
2976
3034
|
}
|
|
2977
3035
|
if (group.length > 1) {
|
|
2978
|
-
group.
|
|
3036
|
+
for (const [idx, groupStmt] of group.entries()) {
|
|
2979
3037
|
context.report({
|
|
2980
3038
|
...(idx === 0
|
|
2981
3039
|
? {
|
|
@@ -2992,7 +3050,7 @@ const rule$2 = createRule$3({
|
|
|
2992
3050
|
messageId: 'preferMultiArgPush',
|
|
2993
3051
|
node: groupStmt,
|
|
2994
3052
|
});
|
|
2995
|
-
}
|
|
3053
|
+
}
|
|
2996
3054
|
}
|
|
2997
3055
|
i = j;
|
|
2998
3056
|
}
|
|
@@ -3087,7 +3145,7 @@ const rule$1 = createRule$2({
|
|
|
3087
3145
|
: importedAs.replace(/(?:Component|Directive)$/, '');
|
|
3088
3146
|
const fullText = sourceCode.getText();
|
|
3089
3147
|
const regex = new RegExp(String.raw `\b${importedAs}\b`, 'g');
|
|
3090
|
-
const usageCount = (fullText.match(regex)
|
|
3148
|
+
const usageCount = (fullText.match(regex) ?? []).length;
|
|
3091
3149
|
const shouldDeleteImport = usageCount <= 2;
|
|
3092
3150
|
context.report({
|
|
3093
3151
|
data: { newName: short, oldName: importedAs },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ui/eslint-plugin-experience-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.453.0",
|
|
4
4
|
"description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"glob": "13.0.6"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@eslint/compat": "^2.0.
|
|
34
|
+
"@eslint/compat": "^2.0.4",
|
|
35
35
|
"@eslint/markdown": "^8.0.1",
|
|
36
|
-
"@html-eslint/eslint-plugin": "^0.
|
|
36
|
+
"@html-eslint/eslint-plugin": "^0.59.0",
|
|
37
37
|
"@html-eslint/parser": "^0.58.1",
|
|
38
38
|
"@smarttools/eslint-plugin-rxjs": "^1.0.22",
|
|
39
39
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
type MessageId = 'mergeLiterals' | 'useTemplate';
|
|
2
|
+
type MessageId = 'flattenTemplate' | 'mergeLiterals' | 'useTemplate';
|
|
3
3
|
export declare const rule: ESLintUtils.RuleModule<MessageId, [], unknown, ESLintUtils.RuleListener> & {
|
|
4
4
|
name: string;
|
|
5
5
|
};
|