@taiga-ui/eslint-plugin-experience-next 0.508.0 → 0.510.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 +0 -58
- package/index.esm.js +176 -4
- package/package.json +1 -1
- package/rules/recommended/prefer-conditional-return.d.ts +2 -2
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 (
|
|
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
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const rule:
|
|
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;
|