@taiga-ui/eslint-plugin-experience-next 0.508.0 → 0.509.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
@@ -127,61 +127,3 @@ from third-party plugins. The exact severities and file globs live in
127
127
  | [single-line-variable-spacing](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/single-line-variable-spacing.md) | Group consecutive single-line variables and separate multiline ones with a blank line | ✅ | 🔧 | |
128
128
  | [standalone-imports-sort](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/standalone-imports-sort.md) | Auto sort names inside Angular decorators | ✅ | 🔧 | |
129
129
  | [strict-tui-doc-example](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/strict-tui-doc-example.md) | If you use the addon-doc, there will be a hint that you are importing something incorrectly | | 🔧 | |
130
-
131
- ## no-empty-style-metadata
132
-
133
- Disallows empty Angular component style metadata. The rule reports empty string and empty template literal values for
134
- `styles` and `styleUrl`, plus `styleUrls: []`, because they do not add component styles and can be safely removed.
135
-
136
- ```ts
137
- // ❌ error
138
- @Component({
139
- selector: 'app-root',
140
- templateUrl: './app.component.html',
141
- styles: ``,
142
- styleUrl: '',
143
- styleUrls: [],
144
- })
145
-
146
- // ✅ after autofix
147
- @Component({
148
- selector: 'app-root',
149
- templateUrl: './app.component.html',
150
- })
151
- ```
152
-
153
- ## prefer-conditional-return
154
-
155
- Prefer a single conditional return when an `if` statement returns one expression and the immediately following statement
156
- returns the fallback expression. The rule skips branches with comments, `else`, empty returns, intervening statements,
157
- or a return expression that is already a conditional expression.
158
-
159
- ```ts
160
- // ❌ error
161
- if (index < count) {
162
- return {value: index++, done: false};
163
- }
164
-
165
- return {value: undefined, done: true};
166
-
167
- // ✅ after autofix
168
- return index < count ? {value: index++, done: false} : {value: undefined, done: true};
169
- ```
170
-
171
- ```ts
172
- // not changed: if branch return is already conditional
173
- if (condition) {
174
- return first ? second : third;
175
- }
176
-
177
- return fallback;
178
- ```
179
-
180
- ```ts
181
- // not changed: fallback return is already conditional
182
- if (condition) {
183
- return value;
184
- }
185
-
186
- return first ? second : third;
187
- ```
package/index.esm.js CHANGED
@@ -532,6 +532,7 @@ var recommended = defineConfig([
532
532
  progress.configs['recommended-ci'],
533
533
  {
534
534
  ignores: [
535
+ '**/tests-results/**',
535
536
  '**/tests-report/**',
536
537
  '**/snapshots/**',
537
538
  '**/test-results/**',
@@ -247942,6 +247943,9 @@ function sourceFileHasDefaultExport(sourceFile) {
247942
247943
  hasRuntimeDefaultModifier(statement) ||
247943
247944
  (ts.isExportDeclaration(statement) && exportsDefaultSpecifier(statement)));
247944
247945
  }
247946
+ function isJsonModuleFileName(fileName) {
247947
+ return path.extname(fileName).toLowerCase() === '.json';
247948
+ }
247945
247949
  function hasDefaultExport(program, fileName) {
247946
247950
  const canonicalFileName = createCanonicalFileName();
247947
247951
  const normalizedFileName = canonicalFileName(fileName);
@@ -248401,7 +248405,8 @@ const rule$N = createRule({
248401
248405
  : null;
248402
248406
  }
248403
248407
  function getIndexPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) {
248404
- if (!isIndexModulePath(moduleSpecifierPath)) {
248408
+ if (codeFileExtensionRegExp.test(moduleSpecifierPath) ||
248409
+ !isIndexModulePath(moduleSpecifierPath)) {
248405
248410
  return null;
248406
248411
  }
248407
248412
  const parentDirectory = path.posix.dirname(moduleSpecifierPath);
@@ -248618,7 +248623,8 @@ const rule$N = createRule({
248618
248623
  const moduleSpecifier = node.source.value;
248619
248624
  const resolved = resolveModule(tsProgram, context.filename, moduleSpecifier);
248620
248625
  if (!resolved ||
248621
- (resolved.isExternalLibraryImport && ignoreExternalDefaultImports)) {
248626
+ (resolved.isExternalLibraryImport && ignoreExternalDefaultImports) ||
248627
+ isJsonModuleFileName(resolved.resolvedFileName)) {
248622
248628
  return;
248623
248629
  }
248624
248630
  const hasResolvedDefaultExport = hasDefaultExport(tsProgram, resolved.resolvedFileName);
@@ -248709,6 +248715,7 @@ const rule$N = createRule({
248709
248715
  const resolved = resolveModule(tsProgram, context.filename, moduleSpecifier);
248710
248716
  if (!resolved ||
248711
248717
  (resolved.isExternalLibraryImport && ignoreExternalDefaultImports) ||
248718
+ isJsonModuleFileName(resolved.resolvedFileName) ||
248712
248719
  !hasDefaultExport(tsProgram, resolved.resolvedFileName)) {
248713
248720
  return;
248714
248721
  }
@@ -248734,7 +248741,11 @@ const rule$N = createRule({
248734
248741
  return;
248735
248742
  }
248736
248743
  const namespaceImport = node.specifiers.find((specifier) => specifier.type === dist$3.AST_NODE_TYPES.ImportNamespaceSpecifier);
248737
- if (!namespaceImport) {
248744
+ if (!namespaceImport || typeof node.source.value !== 'string') {
248745
+ return;
248746
+ }
248747
+ const resolved = resolveModule(tsProgram, context.filename, node.source.value);
248748
+ if (resolved && isJsonModuleFileName(resolved.resolvedFileName)) {
248738
248749
  return;
248739
248750
  }
248740
248751
  const exportNames = getNamespaceImportExportNames(checker, esTreeNodeToTSNodeMap, node);
@@ -251380,6 +251391,34 @@ function getStatementIndent$1(statement, sourceText) {
251380
251391
  const before = sourceText.slice(lineStart, start);
251381
251392
  return /^\s*$/.test(before) ? before : '';
251382
251393
  }
251394
+ function isAstNode(value) {
251395
+ if (!value || typeof value !== 'object' || !('type' in value)) {
251396
+ return false;
251397
+ }
251398
+ const { type } = value;
251399
+ return typeof type === 'string';
251400
+ }
251401
+ function getParent(node) {
251402
+ const maybeNode = node;
251403
+ if (!maybeNode || typeof maybeNode !== 'object' || !('parent' in maybeNode)) {
251404
+ return null;
251405
+ }
251406
+ const { parent } = maybeNode;
251407
+ return isAstNode(parent) ? parent : null;
251408
+ }
251409
+ function isOptionalMemberReceiver(call) {
251410
+ let current = call;
251411
+ let parent = getParent(current);
251412
+ while (parent?.type === dist$3.AST_NODE_TYPES.TSAsExpression ||
251413
+ parent?.type === dist$3.AST_NODE_TYPES.TSNonNullExpression ||
251414
+ parent?.type === dist$3.AST_NODE_TYPES.TSTypeAssertion) {
251415
+ current = parent;
251416
+ parent = getParent(current);
251417
+ }
251418
+ return (parent?.type === dist$3.AST_NODE_TYPES.MemberExpression &&
251419
+ parent.object === current &&
251420
+ parent.optional);
251421
+ }
251383
251422
  const rule$t = createRule({
251384
251423
  create(context) {
251385
251424
  const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
@@ -251396,6 +251435,7 @@ const rule$t = createRule({
251396
251435
  return false;
251397
251436
  }
251398
251437
  if (child.type === dist$3.AST_NODE_TYPES.CallExpression &&
251438
+ !isOptionalMemberReceiver(child) &&
251399
251439
  isSignalReadCall(child, checker, signalNodeMap) &&
251400
251440
  isNullableCallType(child, checker, signalNodeMap)) {
251401
251441
  const text = sourceCode.getText(child);
@@ -252872,6 +252912,18 @@ const rule$m = createRule({
252872
252912
  });
252873
252913
 
252874
252914
  const MAX_INLINE_CONDITIONAL_RETURN_LENGTH = 90;
252915
+ const BOOLEAN_BINARY_OPERATORS = new Set([
252916
+ '!=',
252917
+ '!==',
252918
+ '<',
252919
+ '<=',
252920
+ '==',
252921
+ '===',
252922
+ '>',
252923
+ '>=',
252924
+ 'in',
252925
+ 'instanceof',
252926
+ ]);
252875
252927
  function getReturnStatement(statement) {
252876
252928
  if (statement.type === dist$3.AST_NODE_TYPES.ReturnStatement) {
252877
252929
  return statement;
@@ -252912,6 +252964,52 @@ function isConditionalExpression(node) {
252912
252964
  return (node !== null &&
252913
252965
  unwrapTypeAndParentheses(node).type === dist$3.AST_NODE_TYPES.ConditionalExpression);
252914
252966
  }
252967
+ function isBooleanLiteral(node) {
252968
+ return node.type === dist$3.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
252969
+ }
252970
+ function getBooleanLiteralValue(node) {
252971
+ return isBooleanLiteral(node) ? node.value : null;
252972
+ }
252973
+ function isKnownBooleanExpression(node) {
252974
+ const unwrapped = unwrapTypeAndParentheses(node);
252975
+ switch (unwrapped.type) {
252976
+ case dist$3.AST_NODE_TYPES.BinaryExpression:
252977
+ return BOOLEAN_BINARY_OPERATORS.has(unwrapped.operator);
252978
+ case dist$3.AST_NODE_TYPES.Literal:
252979
+ return typeof unwrapped.value === 'boolean';
252980
+ case dist$3.AST_NODE_TYPES.LogicalExpression:
252981
+ return (isKnownBooleanExpression(unwrapped.left) &&
252982
+ isKnownBooleanExpression(unwrapped.right));
252983
+ case dist$3.AST_NODE_TYPES.UnaryExpression:
252984
+ return unwrapped.operator === '!';
252985
+ default:
252986
+ return false;
252987
+ }
252988
+ }
252989
+ function isBooleanType(type) {
252990
+ const types = type.isUnion() ? type.types : [type];
252991
+ return types.every((part) => !!(part.flags & ts.TypeFlags.BooleanLike));
252992
+ }
252993
+ function getBooleanTestReturnStrategy(test, consequentExpression, alternateExpression, isTypeCheckedBooleanExpression) {
252994
+ const consequentValue = getBooleanLiteralValue(consequentExpression);
252995
+ const alternateValue = getBooleanLiteralValue(alternateExpression);
252996
+ if (consequentValue === false && alternateValue === true) {
252997
+ return unwrapTypeAndParentheses(test).type === dist$3.AST_NODE_TYPES.LogicalExpression
252998
+ ? 'skip'
252999
+ : 'negate';
253000
+ }
253001
+ if (consequentValue !== true || alternateValue !== false) {
253002
+ return 'skip';
253003
+ }
253004
+ return isKnownBooleanExpression(test) || isTypeCheckedBooleanExpression(test)
253005
+ ? 'direct'
253006
+ : 'coerce';
253007
+ }
253008
+ function shouldSkipBooleanReturn(test, consequentExpression, alternateExpression) {
253009
+ return (getBooleanLiteralValue(consequentExpression) === false &&
253010
+ getBooleanLiteralValue(alternateExpression) === true &&
253011
+ unwrapTypeAndParentheses(test).type === dist$3.AST_NODE_TYPES.LogicalExpression);
253012
+ }
252915
253013
  function needsParenthesesInConditionalTest(node) {
252916
253014
  if (getParenthesizedInner(node)) {
252917
253015
  return false;
@@ -252949,6 +253047,27 @@ function needsParenthesesInConditionalBranch(node) {
252949
253047
  return false;
252950
253048
  }
252951
253049
  }
253050
+ function needsParenthesesInBooleanCoercion(node) {
253051
+ if (getParenthesizedInner(node)) {
253052
+ return false;
253053
+ }
253054
+ switch (node.type) {
253055
+ case dist$3.AST_NODE_TYPES.ArrowFunctionExpression:
253056
+ case dist$3.AST_NODE_TYPES.AssignmentExpression:
253057
+ case dist$3.AST_NODE_TYPES.BinaryExpression:
253058
+ case dist$3.AST_NODE_TYPES.ConditionalExpression:
253059
+ case dist$3.AST_NODE_TYPES.LogicalExpression:
253060
+ case dist$3.AST_NODE_TYPES.ObjectExpression:
253061
+ case dist$3.AST_NODE_TYPES.SequenceExpression:
253062
+ case dist$3.AST_NODE_TYPES.TSAsExpression:
253063
+ case dist$3.AST_NODE_TYPES.TSSatisfiesExpression:
253064
+ case dist$3.AST_NODE_TYPES.TSTypeAssertion:
253065
+ case dist$3.AST_NODE_TYPES.YieldExpression:
253066
+ return true;
253067
+ default:
253068
+ return false;
253069
+ }
253070
+ }
252952
253071
  function renderExpression(node, sourceCode, needsParentheses) {
252953
253072
  const text = sourceCode.getText(node);
252954
253073
  return needsParentheses(node) ? `(${text})` : text;
@@ -252972,9 +253091,58 @@ function renderConditionalReturn(ifStatement, consequentExpression, alternateExp
252972
253091
  const branchIndent = `${indent} `;
252973
253092
  return `return ${test}\n${branchIndent}? ${consequent}\n${branchIndent}: ${alternate};`;
252974
253093
  }
253094
+ function renderBooleanTestReturn(ifStatement, sourceCode, strategy) {
253095
+ const test = sourceCode.getText(ifStatement.test);
253096
+ if (strategy === 'negate') {
253097
+ if (!test.includes('\n')) {
253098
+ const renderedTest = needsParenthesesInBooleanCoercion(ifStatement.test)
253099
+ ? `(${test})`
253100
+ : test;
253101
+ return `return !${renderedTest};`;
253102
+ }
253103
+ const indent = getStatementIndent(ifStatement, sourceCode);
253104
+ return `return !(\n${indent} ${test}\n${indent});`;
253105
+ }
253106
+ if (strategy === 'coerce') {
253107
+ if (!test.includes('\n')) {
253108
+ const renderedTest = needsParenthesesInBooleanCoercion(ifStatement.test)
253109
+ ? `(${test})`
253110
+ : test;
253111
+ return `return !!${renderedTest};`;
253112
+ }
253113
+ const indent = getStatementIndent(ifStatement, sourceCode);
253114
+ return `return !!(\n${indent} ${test}\n${indent});`;
253115
+ }
253116
+ if (!test.includes('\n')) {
253117
+ return `return ${test};`;
253118
+ }
253119
+ const indent = getStatementIndent(ifStatement, sourceCode);
253120
+ return `return (\n${indent} ${test}\n${indent});`;
253121
+ }
252975
253122
  const rule$l = createRule({
252976
253123
  create(context) {
252977
253124
  const { sourceCode } = context;
253125
+ let parserServices = null;
253126
+ let checker = null;
253127
+ try {
253128
+ parserServices = dist$3.ESLintUtils.getParserServices(context);
253129
+ checker = parserServices.program.getTypeChecker();
253130
+ }
253131
+ catch {
253132
+ // Type checking is optional; syntactic boolean detection still works.
253133
+ }
253134
+ function isTypeCheckedBooleanExpression(node) {
253135
+ if (!parserServices || !checker) {
253136
+ return false;
253137
+ }
253138
+ try {
253139
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
253140
+ return isBooleanType(checker.getTypeAtLocation(tsNode));
253141
+ }
253142
+ catch {
253143
+ return false;
253144
+ }
253145
+ }
252978
253146
  function checkBody(statements) {
252979
253147
  for (const [index, statement] of statements.entries()) {
252980
253148
  if (statement.type !== dist$3.AST_NODE_TYPES.IfStatement) {
@@ -252993,6 +253161,7 @@ const rule$l = createRule({
252993
253161
  !finalArgument ||
252994
253162
  isConditionalExpression(consequentArgument) ||
252995
253163
  isConditionalExpression(finalArgument) ||
253164
+ shouldSkipBooleanReturn(statement.test, consequentArgument, finalArgument) ||
252996
253165
  !hasOnlyWhitespaceBetween(sourceCode, statement, finalReturn) ||
252997
253166
  sourceCode.getCommentsInside(statement).length > 0 ||
252998
253167
  sourceCode.getCommentsInside(finalReturn).length > 0) {
@@ -253000,7 +253169,10 @@ const rule$l = createRule({
253000
253169
  }
253001
253170
  context.report({
253002
253171
  fix(fixer) {
253003
- const replacement = renderConditionalReturn(statement, consequentArgument, finalArgument, sourceCode);
253172
+ const booleanTestReturnStrategy = getBooleanTestReturnStrategy(statement.test, consequentArgument, finalArgument, isTypeCheckedBooleanExpression);
253173
+ const replacement = booleanTestReturnStrategy === 'skip'
253174
+ ? renderConditionalReturn(statement, consequentArgument, finalArgument, sourceCode)
253175
+ : renderBooleanTestReturn(statement, sourceCode, booleanTestReturnStrategy);
253004
253176
  return fixer.replaceTextRange([statement.range[0], finalReturn.range[1]], replacement);
253005
253177
  },
253006
253178
  messageId: 'preferConditionalReturn',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.508.0",
3
+ "version": "0.509.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
- import { type TSESLint } from '@typescript-eslint/utils';
2
- export declare const rule: TSESLint.RuleModule<"preferConditionalReturn", [], unknown, TSESLint.RuleListener> & {
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const rule: ESLintUtils.RuleModule<"preferConditionalReturn", [], unknown, ESLintUtils.RuleListener> & {
3
3
  name: string;
4
4
  };
5
5
  export default rule;