@taiga-ui/eslint-plugin-experience-next 0.505.0 → 0.506.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
@@ -109,6 +109,7 @@ from third-party plugins. The exact severities and file globs live in
109
109
  | [object-single-line](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/object-single-line.md) | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
110
110
  | [prefer-combined-if-control-flow](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-combined-if-control-flow.md) | Combine consecutive `if` statements that use the same `return`, `break`, `continue`, or `throw` | ✅ | 🔧 | |
111
111
  | [prefer-deep-imports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-deep-imports.md) | Allow deep imports of Taiga UI packages | | 🔧 | |
112
+ | [prefer-loose-null-check](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-loose-null-check.md) | Prefer loose null checks over paired strict comparisons against `null` and `undefined` | ✅ | 🔧 | |
112
113
  | [prefer-multi-arg-push](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-multi-arg-push.md) | Combine consecutive `.push()` calls on the same array into a single multi-argument call | ✅ | 🔧 | |
113
114
  | [prefer-namespace-keyword](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-namespace-keyword.md) | Replace `module Foo {}` with `namespace Foo {}` for TypeScript namespace declarations | ✅ | 🔧 | |
114
115
  | [prefer-untracked-incidental-signal-reads](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-untracked-incidental-signal-reads.md) | Wrap likely-incidental signal reads with `untracked()` in reactive callbacks | ✅ | 🔧 | |
package/index.d.ts CHANGED
@@ -148,6 +148,9 @@ declare const plugin: {
148
148
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
149
149
  name: string;
150
150
  };
151
+ 'prefer-loose-null-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferLooseNullCheck", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
152
+ name: string;
153
+ };
151
154
  'prefer-multi-arg-push': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferMultiArgPush", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
152
155
  name: string;
153
156
  };
package/index.esm.js CHANGED
@@ -1213,6 +1213,7 @@ var recommended = defineConfig([
1213
1213
  '@taiga-ui/experience-next/no-useless-untracked': 'error',
1214
1214
  '@taiga-ui/experience-next/object-single-line': ['error', { printWidth: 90 }],
1215
1215
  '@taiga-ui/experience-next/prefer-combined-if-control-flow': 'error',
1216
+ '@taiga-ui/experience-next/prefer-loose-null-check': 'error',
1216
1217
  '@taiga-ui/experience-next/prefer-multi-arg-push': 'error',
1217
1218
  '@taiga-ui/experience-next/prefer-namespace-keyword': 'error',
1218
1219
  '@taiga-ui/experience-next/prefer-untracked-incidental-signal-reads': 'error',
@@ -46223,7 +46224,7 @@ function buildMultilineStartTag(node, sourceText) {
46223
46224
  closing,
46224
46225
  ].join('\n');
46225
46226
  }
46226
- const rule$P = createRule({
46227
+ const rule$Q = createRule({
46227
46228
  name: 'attrs-newline',
46228
46229
  rule: {
46229
46230
  create(context) {
@@ -46370,7 +46371,7 @@ const config$5 = {
46370
46371
  function getCorrectOrderRelative(correct, current) {
46371
46372
  return correct.filter((item) => current.includes(item));
46372
46373
  }
46373
- const rule$O = createRule({
46374
+ const rule$P = createRule({
46374
46375
  name: 'decorator-key-sort',
46375
46376
  rule: config$5,
46376
46377
  });
@@ -46434,7 +46435,7 @@ function getNodeLabel(node) {
46434
46435
  }
46435
46436
  return 'text';
46436
46437
  }
46437
- const rule$N = createRule({
46438
+ const rule$O = createRule({
46438
46439
  name: 'element-newline',
46439
46440
  rule: {
46440
46441
  create(context) {
@@ -46665,7 +46666,7 @@ const PRESETS = {
46665
46666
  $VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
46666
46667
  $VUE_ATTRIBUTE: /^v-/,
46667
46668
  };
46668
- const rule$M = createRule({
46669
+ const rule$N = createRule({
46669
46670
  create(context, [options]) {
46670
46671
  const sourceCode = context.sourceCode;
46671
46672
  const settings = {
@@ -46779,7 +46780,7 @@ function getHostAttributeProperties(hostObject) {
46779
46780
  return null;
46780
46781
  }
46781
46782
  const name = getStaticPropertyName(property.key);
46782
- if (name === null) {
46783
+ if (name == null) {
46783
46784
  return null;
46784
46785
  }
46785
46786
  properties.push({ name, node: property });
@@ -46998,7 +46999,7 @@ const config$4 = {
46998
46999
  type: 'suggestion',
46999
47000
  },
47000
47001
  };
47001
- const rule$L = createRule({
47002
+ const rule$M = createRule({
47002
47003
  name: 'html-logical-properties',
47003
47004
  rule: config$4,
47004
47005
  });
@@ -248250,7 +248251,7 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
248250
248251
  }
248251
248252
  return hasSafeRuntimeUsage;
248252
248253
  }
248253
- const rule$K = createRule({
248254
+ const rule$L = createRule({
248254
248255
  create(context) {
248255
248256
  const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
248256
248257
  const checkCycles = context.options[0]?.checkCycles ?? true;
@@ -248956,7 +248957,7 @@ function prependTokenName(text, name) {
248956
248957
  return `${text.slice(0, 1)}[${name}]: ${text.slice(1)}`;
248957
248958
  }
248958
248959
  function isNgDevModeVisible(sourceCode, node) {
248959
- for (let scope = sourceCode.getScope(node); scope !== null; scope = scope.upper) {
248960
+ for (let scope = sourceCode.getScope(node); scope != null; scope = scope.upper) {
248960
248961
  if (scope.variables.some((variable) => variable.name === NG_DEV_MODE)) {
248961
248962
  return true;
248962
248963
  }
@@ -248976,7 +248977,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
248976
248977
  }
248977
248978
  return fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
248978
248979
  }
248979
- const rule$J = createRule({
248980
+ const rule$K = createRule({
248980
248981
  create(context) {
248981
248982
  const { sourceCode } = context;
248982
248983
  const program = sourceCode.ast;
@@ -249025,7 +249026,7 @@ const rule$J = createRule({
249025
249026
  name: 'injection-token-description',
249026
249027
  });
249027
249028
 
249028
- const rule$I = createRule({
249029
+ const rule$J = createRule({
249029
249030
  create(context) {
249030
249031
  const { sourceCode } = context;
249031
249032
  const namespaceImports = new Map();
@@ -249120,7 +249121,7 @@ const DEFAULT_OPTIONS = {
249120
249121
  importDeclaration: '^@taiga-ui*',
249121
249122
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
249122
249123
  };
249123
- const rule$H = createRule({
249124
+ const rule$I = createRule({
249124
249125
  create(context) {
249125
249126
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
249126
249127
  const hasNonCodeExtension = (source) => {
@@ -249212,7 +249213,7 @@ const nearestFileUpCache = new Map();
249212
249213
  const markerCache = new Map();
249213
249214
  const indexFileCache = new Map();
249214
249215
  const indexExportsCache = new Map();
249215
- const rule$G = createRule({
249216
+ const rule$H = createRule({
249216
249217
  create(context) {
249217
249218
  const parserServices = dist$3.ESLintUtils.getParserServices(context);
249218
249219
  const program = parserServices.program;
@@ -249227,7 +249228,7 @@ const rule$G = createRule({
249227
249228
  }
249228
249229
  const key = `${containingDir}\0${moduleSpecifier}`;
249229
249230
  const cachedFile = cache.get(key);
249230
- if (cachedFile !== undefined) {
249231
+ if (cachedFile != null) {
249231
249232
  return cachedFile;
249232
249233
  }
249233
249234
  const resolved = ts.resolveModuleName(moduleSpecifier, context.filename, compilerOptions, compilerHost);
@@ -249238,7 +249239,7 @@ const rule$G = createRule({
249238
249239
  function findNearestFileUpwardsCached(startDirectory, fileName) {
249239
249240
  const key = `${startDirectory}\0${fileName}`;
249240
249241
  const cachedPath = nearestFileUpCache.get(key);
249241
- if (cachedPath !== undefined) {
249242
+ if (cachedPath != null) {
249242
249243
  return cachedPath;
249243
249244
  }
249244
249245
  let currentDirectory = startDirectory;
@@ -249259,7 +249260,7 @@ const rule$G = createRule({
249259
249260
  }
249260
249261
  function pickPackageMarkerFileCached(resolvedRootFilePath) {
249261
249262
  const cachedMarker = markerCache.get(resolvedRootFilePath);
249262
- if (cachedMarker !== undefined) {
249263
+ if (cachedMarker != null) {
249263
249264
  return cachedMarker;
249264
249265
  }
249265
249266
  const resolvedRootDirectory = path.dirname(resolvedRootFilePath);
@@ -249271,7 +249272,7 @@ const rule$G = createRule({
249271
249272
  }
249272
249273
  function pickIndexFileInDirectoryCached(packageDirectory) {
249273
249274
  const cachedIndexFile = indexFileCache.get(packageDirectory);
249274
- if (cachedIndexFile !== undefined) {
249275
+ if (cachedIndexFile != null) {
249275
249276
  return cachedIndexFile;
249276
249277
  }
249277
249278
  const indexTypescriptPath = path.join(packageDirectory, 'index.ts');
@@ -249409,13 +249410,13 @@ const noDuplicateAttributesRule = angular.templatePlugin.rules?.['no-duplicate-a
249409
249410
  if (!noDuplicateAttributesRule) {
249410
249411
  throw new Error('angular-eslint template rule "no-duplicate-attributes" is not available');
249411
249412
  }
249412
- const rule$F = createRule({
249413
+ const rule$G = createRule({
249413
249414
  name: 'no-duplicate-attrs',
249414
249415
  rule: noDuplicateAttributesRule,
249415
249416
  });
249416
249417
 
249417
249418
  const MESSAGE_ID$c = 'duplicateId';
249418
- const rule$E = createRule({
249419
+ const rule$F = createRule({
249419
249420
  name: 'no-duplicate-id',
249420
249421
  rule: {
249421
249422
  create(context) {
@@ -249477,7 +249478,7 @@ function getTrackingKey(node) {
249477
249478
  }
249478
249479
  return null;
249479
249480
  }
249480
- const rule$D = createRule({
249481
+ const rule$E = createRule({
249481
249482
  name: 'no-duplicate-in-head',
249482
249483
  rule: {
249483
249484
  create(context) {
@@ -249543,7 +249544,7 @@ function getOrderedChildren(node) {
249543
249544
  ...node.params,
249544
249545
  node.body,
249545
249546
  ];
249546
- return children.filter((child) => child !== undefined && child !== null);
249547
+ return children.filter((child) => child != null);
249547
249548
  }
249548
249549
  if (node.type === dist$3.AST_NODE_TYPES.BlockStatement ||
249549
249550
  node.type === dist$3.AST_NODE_TYPES.Program) {
@@ -249570,7 +249571,7 @@ function getOrderedChildren(node) {
249570
249571
  node.update,
249571
249572
  node.body,
249572
249573
  ];
249573
- return children.filter((child) => child !== null);
249574
+ return children.filter((child) => child != null);
249574
249575
  }
249575
249576
  if (node.type === dist$3.AST_NODE_TYPES.ForInStatement ||
249576
249577
  node.type === dist$3.AST_NODE_TYPES.ForOfStatement) {
@@ -250062,7 +250063,7 @@ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#r
250062
250063
  const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
250063
250064
  const createUntrackedRule = createRule;
250064
250065
 
250065
- const rule$C = createUntrackedRule({
250066
+ const rule$D = createUntrackedRule({
250066
250067
  create(context) {
250067
250068
  const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
250068
250069
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -250135,7 +250136,7 @@ const config$3 = {
250135
250136
  type: 'problem',
250136
250137
  },
250137
250138
  };
250138
- const rule$B = createRule({
250139
+ const rule$C = createRule({
250139
250140
  name: 'no-href-with-router-link',
250140
250141
  rule: config$3,
250141
250142
  });
@@ -250196,7 +250197,7 @@ function getScopeRoot(node) {
250196
250197
  return (findAncestor(node, (ancestor) => ancestor.type === dist$3.AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
250197
250198
  }
250198
250199
 
250199
- const rule$A = createRule({
250200
+ const rule$B = createRule({
250200
250201
  create(context) {
250201
250202
  const checkImplicitPublic = (node) => {
250202
250203
  const classRef = getEnclosingClass(node);
@@ -250258,7 +250259,7 @@ const rule$A = createRule({
250258
250259
  name: 'no-implicit-public',
250259
250260
  });
250260
250261
 
250261
- const rule$z = createRule({
250262
+ const rule$A = createRule({
250262
250263
  create(context) {
250263
250264
  const { sourceCode } = context;
250264
250265
  return {
@@ -250314,9 +250315,9 @@ function isInfiniteLoopLiteral(node) {
250314
250315
  return unwrapped.value === true || unwrapped.value === 1;
250315
250316
  }
250316
250317
  function isInfiniteLoopTest(test) {
250317
- return test === null || isInfiniteLoopLiteral(test);
250318
+ return test == null || isInfiniteLoopLiteral(test);
250318
250319
  }
250319
- const rule$y = createRule({
250320
+ const rule$z = createRule({
250320
250321
  create(context) {
250321
250322
  return {
250322
250323
  DoWhileStatement(node) {
@@ -250361,7 +250362,7 @@ const rule$y = createRule({
250361
250362
  });
250362
250363
 
250363
250364
  const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
250364
- const rule$x = createRule({
250365
+ const rule$y = createRule({
250365
250366
  create(context) {
250366
250367
  return {
250367
250368
  Program(node) {
@@ -250821,7 +250822,7 @@ const OBSOLETE_HTML_ATTRS = {
250821
250822
  };
250822
250823
 
250823
250824
  const MESSAGE_ID$9 = 'obsolete';
250824
- const rule$w = createRule({
250825
+ const rule$x = createRule({
250825
250826
  name: 'no-obsolete-attrs',
250826
250827
  rule: {
250827
250828
  create(context) {
@@ -250899,7 +250900,7 @@ const OBSOLETE_HTML_TAGS = new Set([
250899
250900
  ]);
250900
250901
 
250901
250902
  const MESSAGE_ID$8 = 'unexpected';
250902
- const rule$v = createRule({
250903
+ const rule$w = createRule({
250903
250904
  name: 'no-obsolete-tags',
250904
250905
  rule: {
250905
250906
  create(context) {
@@ -250926,7 +250927,7 @@ const rule$v = createRule({
250926
250927
  },
250927
250928
  });
250928
250929
 
250929
- const rule$u = createRule({
250930
+ const rule$v = createRule({
250930
250931
  create(context) {
250931
250932
  const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
250932
250933
  return {
@@ -251070,7 +251071,7 @@ const config$2 = {
251070
251071
  type: 'problem',
251071
251072
  },
251072
251073
  };
251073
- const rule$t = createRule({
251074
+ const rule$u = createRule({
251074
251075
  name: 'no-project-as-in-ng-template',
251075
251076
  rule: config$2,
251076
251077
  });
@@ -251107,7 +251108,7 @@ function collectArrayExpressions(node) {
251107
251108
  }
251108
251109
  return result;
251109
251110
  }
251110
- const rule$s = createRule({
251111
+ const rule$t = createRule({
251111
251112
  create(context) {
251112
251113
  const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
251113
251114
  const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
@@ -251282,7 +251283,7 @@ function getStatementIndent(statement, sourceText) {
251282
251283
  const before = sourceText.slice(lineStart, start);
251283
251284
  return /^\s*$/.test(before) ? before : '';
251284
251285
  }
251285
- const rule$r = createRule({
251286
+ const rule$s = createRule({
251286
251287
  create(context) {
251287
251288
  const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
251288
251289
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -251713,7 +251714,7 @@ function inspectComputedBody(root, context, localScopes, visitedFunctions, repor
251713
251714
  return;
251714
251715
  });
251715
251716
  }
251716
- const rule$q = createRule({
251717
+ const rule$r = createRule({
251717
251718
  create(context) {
251718
251719
  const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
251719
251720
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -251756,7 +251757,7 @@ const rule$q = createRule({
251756
251757
  name: 'no-side-effects-in-computed',
251757
251758
  });
251758
251759
 
251759
- const rule$p = createUntrackedRule({
251760
+ const rule$q = createUntrackedRule({
251760
251761
  create(context) {
251761
251762
  const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
251762
251763
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -251850,7 +251851,7 @@ function templateContent(template, renderExpr) {
251850
251851
  : ''}`)
251851
251852
  .join('');
251852
251853
  }
251853
- const rule$o = createRule({
251854
+ const rule$p = createRule({
251854
251855
  create(context) {
251855
251856
  const { sourceCode } = context;
251856
251857
  let parserServices = null;
@@ -252185,7 +252186,7 @@ function buildReactiveCallReplacement(outerUntrackedCall, reactiveCall, sourceCo
252185
252186
  }
252186
252187
  return dedent(text, reactiveCall.loc.start.column - outerUntrackedCall.parent.loc.start.column);
252187
252188
  }
252188
- const rule$n = createUntrackedRule({
252189
+ const rule$o = createUntrackedRule({
252189
252190
  create(context) {
252190
252191
  const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
252191
252192
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -252221,7 +252222,7 @@ const rule$n = createUntrackedRule({
252221
252222
  fixer.replaceText(node, buildReactiveCallReplacement(node, reactiveCall, sourceCode)),
252222
252223
  ];
252223
252224
  const untrackedLocalName = findUntrackedAlias(program);
252224
- const stillUsed = untrackedLocalName !== null &&
252225
+ const stillUsed = untrackedLocalName != null &&
252225
252226
  isUntrackedUsedElsewhere(untrackedLocalName, node);
252226
252227
  if (!stillUsed) {
252227
252228
  fixes.push(...buildImportRemovalFixes(program, fixer, sourceCode));
@@ -252314,7 +252315,7 @@ function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program
252314
252315
  });
252315
252316
  return found;
252316
252317
  }
252317
- const rule$m = createUntrackedRule({
252318
+ const rule$n = createUntrackedRule({
252318
252319
  create(context) {
252319
252320
  const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
252320
252321
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -252345,11 +252346,11 @@ const rule$m = createUntrackedRule({
252345
252346
  ? (fixer) => {
252346
252347
  const parentStmt = parent;
252347
252348
  const replacement = buildReplacement(untrackedCall, parentStmt, sourceCode);
252348
- if (replacement === null) {
252349
+ if (replacement == null) {
252349
252350
  return null;
252350
252351
  }
252351
252352
  const untrackedLocalName = findUntrackedAlias(program);
252352
- const stillUsed = untrackedLocalName !== null &&
252353
+ const stillUsed = untrackedLocalName != null &&
252353
252354
  isUntrackedUsedElsewhere(untrackedLocalName, untrackedCall);
252354
252355
  const fixes = [fixer.replaceText(parentStmt, replacement)];
252355
252356
  if (!stillUsed) {
@@ -252396,7 +252397,7 @@ const rule$m = createUntrackedRule({
252396
252397
  name: 'no-useless-untracked',
252397
252398
  });
252398
252399
 
252399
- const rule$l = createRule({
252400
+ const rule$m = createRule({
252400
252401
  create(context, [{ printWidth }]) {
252401
252402
  const sourceCode = context.sourceCode;
252402
252403
  const getLineEndIndex = (lineStartIndex) => {
@@ -252717,7 +252718,7 @@ function renderTest(node, sourceCode) {
252717
252718
  const text = sourceCode.getText(node);
252718
252719
  return needsParenthesesInOrChain(node) ? `(${text})` : text;
252719
252720
  }
252720
- const rule$k = createRule({
252721
+ const rule$l = createRule({
252721
252722
  create(context) {
252722
252723
  const { sourceCode } = context;
252723
252724
  function checkBody(statements) {
@@ -252806,6 +252807,110 @@ const rule$k = createRule({
252806
252807
  name: 'prefer-combined-if-control-flow',
252807
252808
  });
252808
252809
 
252810
+ function parseStrictNullCheck(node, getText) {
252811
+ if (node.type !== dist$3.AST_NODE_TYPES.BinaryExpression) {
252812
+ return null;
252813
+ }
252814
+ const { left, operator, right } = node;
252815
+ if (operator !== '!==' && operator !== '===') {
252816
+ return null;
252817
+ }
252818
+ if (right.type === dist$3.AST_NODE_TYPES.Literal && right.value === null) {
252819
+ return { comparedWith: 'null', operand: getText(left), strictOp: operator };
252820
+ }
252821
+ if (right.type === dist$3.AST_NODE_TYPES.Identifier && right.name === 'undefined') {
252822
+ return { comparedWith: 'undefined', operand: getText(left), strictOp: operator };
252823
+ }
252824
+ if (left.type === dist$3.AST_NODE_TYPES.Literal && left.value === null) {
252825
+ return { comparedWith: 'null', operand: getText(right), strictOp: operator };
252826
+ }
252827
+ if (left.type === dist$3.AST_NODE_TYPES.Identifier && left.name === 'undefined') {
252828
+ return { comparedWith: 'undefined', operand: getText(right), strictOp: operator };
252829
+ }
252830
+ return null;
252831
+ }
252832
+ function isParsedNullCheck(value) {
252833
+ return value !== null;
252834
+ }
252835
+ function getLooseNullCheck(left, right) {
252836
+ if (!isParsedNullCheck(left) ||
252837
+ !isParsedNullCheck(right) ||
252838
+ left.strictOp !== right.strictOp ||
252839
+ left.operand !== right.operand ||
252840
+ left.comparedWith === right.comparedWith) {
252841
+ return null;
252842
+ }
252843
+ return `${left.operand} ${left.strictOp === '!==' ? '!=' : '=='} null`;
252844
+ }
252845
+ function collectAndLeaves(node) {
252846
+ if (node.type === dist$3.AST_NODE_TYPES.LogicalExpression && node.operator === '&&') {
252847
+ return [...collectAndLeaves(node.left), ...collectAndLeaves(node.right)];
252848
+ }
252849
+ return [node];
252850
+ }
252851
+ function isAndChainRoot(node) {
252852
+ return (node.parent.type !== dist$3.AST_NODE_TYPES.LogicalExpression ||
252853
+ node.parent.operator !== '&&');
252854
+ }
252855
+ function findLooseNullCheckMatch(parsedChecks) {
252856
+ for (const [firstCheckIndex, firstCheck] of parsedChecks.entries()) {
252857
+ if (!isParsedNullCheck(firstCheck)) {
252858
+ continue;
252859
+ }
252860
+ for (let secondCheckIndex = firstCheckIndex + 1; secondCheckIndex < parsedChecks.length; secondCheckIndex++) {
252861
+ const replacement = getLooseNullCheck(firstCheck, parsedChecks[secondCheckIndex] ?? null);
252862
+ if (replacement !== null) {
252863
+ return { firstCheckIndex, replacement, secondCheckIndex };
252864
+ }
252865
+ }
252866
+ }
252867
+ return null;
252868
+ }
252869
+ const rule$k = createRule({
252870
+ create(context) {
252871
+ const getText = (n) => context.sourceCode.getText(n);
252872
+ return {
252873
+ /**
252874
+ * Handles only paired null/undefined checks in the same `&&` chain.
252875
+ */
252876
+ LogicalExpression(node) {
252877
+ if (node.operator !== '&&' || !isAndChainRoot(node)) {
252878
+ return;
252879
+ }
252880
+ const leaves = collectAndLeaves(node);
252881
+ const parsedChecks = leaves.map((leaf) => parseStrictNullCheck(leaf, getText));
252882
+ const match = findLooseNullCheckMatch(parsedChecks);
252883
+ if (match === null) {
252884
+ return;
252885
+ }
252886
+ const replacement = leaves
252887
+ .filter((_, index) => index !== match.secondCheckIndex)
252888
+ .map((leaf, index) => index === match.firstCheckIndex
252889
+ ? match.replacement
252890
+ : getText(leaf))
252891
+ .join(' && ');
252892
+ context.report({
252893
+ fix: (fixer) => fixer.replaceText(node, replacement),
252894
+ messageId: 'preferLooseNullCheck',
252895
+ node,
252896
+ });
252897
+ },
252898
+ };
252899
+ },
252900
+ meta: {
252901
+ docs: {
252902
+ description: 'Prefer loose null checks over paired strict comparisons against `null` and `undefined`.',
252903
+ },
252904
+ fixable: 'code',
252905
+ messages: {
252906
+ preferLooseNullCheck: 'Prefer loose null check over paired strict comparisons against `null` and `undefined`.',
252907
+ },
252908
+ schema: [],
252909
+ type: 'suggestion',
252910
+ },
252911
+ name: 'prefer-loose-null-check',
252912
+ });
252913
+
252809
252914
  function getPushCall(node) {
252810
252915
  if (node.expression.type !== dist$3.AST_NODE_TYPES.CallExpression) {
252811
252916
  return null;
@@ -253191,7 +253296,7 @@ const rule$h = createUntrackedRule({
253191
253296
  const estreeNodeMap = tsNodeToESTreeNodeMap;
253192
253297
  function buildFix(read) {
253193
253298
  const untrackedAlias = findUntrackedAlias(program);
253194
- const alreadyHasUntracked = untrackedAlias !== null;
253299
+ const alreadyHasUntracked = untrackedAlias != null;
253195
253300
  const wrapped = buildUntrackedWrap(read, sourceCode, untrackedAlias ?? 'untracked');
253196
253301
  if (alreadyHasUntracked) {
253197
253302
  return (fixer) => [fixer.replaceText(read, wrapped)];
@@ -253521,13 +253626,13 @@ const VALID_CONTAINERS = new Set(['menu', 'ol', 'ul']);
253521
253626
  */
253522
253627
  function isElement(node) {
253523
253628
  return (typeof node === 'object' &&
253524
- node !== null &&
253629
+ node != null &&
253525
253630
  typeof node['name'] === 'string' &&
253526
253631
  Array.isArray(node['children']));
253527
253632
  }
253528
253633
  function getClosestParentElement(node) {
253529
253634
  let current = node['parent'];
253530
- while (current !== null && current !== undefined) {
253635
+ while (current != null) {
253531
253636
  if (isElement(current)) {
253532
253637
  return current;
253533
253638
  }
@@ -254199,7 +254304,7 @@ const rule$6 = createRule({
254199
254304
  if (!arr) {
254200
254305
  continue;
254201
254306
  }
254202
- const elements = arr.elements.filter((el) => el !== null);
254307
+ const elements = arr.elements.filter((el) => el != null);
254203
254308
  if (elements.length <= 1) {
254204
254309
  continue;
254205
254310
  }
@@ -254445,7 +254550,7 @@ const rule$3 = createRule({
254445
254550
  const purityCache = new WeakMap();
254446
254551
  const isPureArray = (arr) => {
254447
254552
  const cachedPurity = purityCache.get(arr);
254448
- if (cachedPurity !== undefined) {
254553
+ if (cachedPurity != null) {
254449
254554
  return cachedPurity;
254450
254555
  }
254451
254556
  if (arr.isDirty) {
@@ -254614,7 +254719,7 @@ const config = {
254614
254719
  }
254615
254720
  for (const attr of node.inputs) {
254616
254721
  const attrValue = getLiteralStringValue(attr);
254617
- if (attrValue !== null) {
254722
+ if (attrValue != null) {
254618
254723
  checkAttribute(context, attr, attrValue, checkers);
254619
254724
  }
254620
254725
  }
@@ -255120,42 +255225,43 @@ const plugin = {
255120
255225
  },
255121
255226
  rules: {
255122
255227
  'array-as-const': rule$5,
255123
- 'attrs-newline': rule$P,
255228
+ 'attrs-newline': rule$Q,
255124
255229
  'class-property-naming': rule$4,
255125
- 'decorator-key-sort': rule$O,
255126
- 'element-newline': rule$N,
255230
+ 'decorator-key-sort': rule$P,
255231
+ 'element-newline': rule$O,
255127
255232
  'flat-exports': rule$3,
255128
- 'host-attributes-sort': rule$M,
255129
- 'html-logical-properties': rule$L,
255130
- 'import-integrity': rule$K,
255131
- 'injection-token-description': rule$J,
255132
- 'no-commonjs-import-patterns': rule$I,
255133
- 'no-deep-imports': rule$H,
255134
- 'no-deep-imports-to-indexed-packages': rule$G,
255135
- 'no-duplicate-attrs': rule$F,
255136
- 'no-duplicate-id': rule$E,
255137
- 'no-duplicate-in-head': rule$D,
255138
- 'no-fully-untracked-effect': rule$C,
255139
- 'no-href-with-router-link': rule$B,
255140
- 'no-implicit-public': rule$A,
255141
- 'no-import-assertions': rule$z,
255142
- 'no-infinite-loop': rule$y,
255143
- 'no-legacy-peer-deps': rule$x,
255144
- 'no-obsolete-attrs': rule$w,
255145
- 'no-obsolete-tags': rule$v,
255146
- 'no-playwright-empty-fill': rule$u,
255147
- 'no-project-as-in-ng-template': rule$t,
255148
- 'no-redundant-type-annotation': rule$s,
255149
- 'no-repeated-signal-in-conditional': rule$r,
255233
+ 'host-attributes-sort': rule$N,
255234
+ 'html-logical-properties': rule$M,
255235
+ 'import-integrity': rule$L,
255236
+ 'injection-token-description': rule$K,
255237
+ 'no-commonjs-import-patterns': rule$J,
255238
+ 'no-deep-imports': rule$I,
255239
+ 'no-deep-imports-to-indexed-packages': rule$H,
255240
+ 'no-duplicate-attrs': rule$G,
255241
+ 'no-duplicate-id': rule$F,
255242
+ 'no-duplicate-in-head': rule$E,
255243
+ 'no-fully-untracked-effect': rule$D,
255244
+ 'no-href-with-router-link': rule$C,
255245
+ 'no-implicit-public': rule$B,
255246
+ 'no-import-assertions': rule$A,
255247
+ 'no-infinite-loop': rule$z,
255248
+ 'no-legacy-peer-deps': rule$y,
255249
+ 'no-obsolete-attrs': rule$x,
255250
+ 'no-obsolete-tags': rule$w,
255251
+ 'no-playwright-empty-fill': rule$v,
255252
+ 'no-project-as-in-ng-template': rule$u,
255253
+ 'no-redundant-type-annotation': rule$t,
255254
+ 'no-repeated-signal-in-conditional': rule$s,
255150
255255
  'no-restricted-attr-values': rule$2,
255151
- 'no-side-effects-in-computed': rule$q,
255152
- 'no-signal-reads-after-await-in-reactive-context': rule$p,
255153
- 'no-string-literal-concat': rule$o,
255154
- 'no-untracked-outside-reactive-context': rule$n,
255155
- 'no-useless-untracked': rule$m,
255156
- 'object-single-line': rule$l,
255157
- 'prefer-combined-if-control-flow': rule$k,
255256
+ 'no-side-effects-in-computed': rule$r,
255257
+ 'no-signal-reads-after-await-in-reactive-context': rule$q,
255258
+ 'no-string-literal-concat': rule$p,
255259
+ 'no-untracked-outside-reactive-context': rule$o,
255260
+ 'no-useless-untracked': rule$n,
255261
+ 'object-single-line': rule$m,
255262
+ 'prefer-combined-if-control-flow': rule$l,
255158
255263
  'prefer-deep-imports': rule$1,
255264
+ 'prefer-loose-null-check': rule$k,
255159
255265
  'prefer-multi-arg-push': rule$j,
255160
255266
  'prefer-namespace-keyword': rule$i,
255161
255267
  'prefer-untracked-incidental-signal-reads': rule$h,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.505.0",
3
+ "version": "0.506.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,4 @@
1
+ export declare const rule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferLooseNullCheck", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
2
+ name: string;
3
+ };
4
+ export default rule;