eslint-plugin-th-rules 2.7.1 → 2.8.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.
Files changed (74) hide show
  1. package/README.md +171 -11
  2. package/dist/configs/bundles/recommended-react.d.ts +199 -0
  3. package/dist/configs/bundles/recommended-react.js +11 -0
  4. package/dist/configs/bundles/recommended-typescript.d.ts +190 -0
  5. package/dist/configs/bundles/recommended-typescript.js +6 -0
  6. package/dist/configs/bundles/recommended.d.ts +190 -0
  7. package/dist/configs/bundles/recommended.js +8 -0
  8. package/dist/configs/core/base.d.ts +170 -0
  9. package/dist/configs/core/base.js +23 -0
  10. package/dist/configs/core/react.d.ts +6 -0
  11. package/dist/configs/core/react.js +8 -0
  12. package/dist/configs/core/typescript.d.ts +2 -0
  13. package/dist/configs/core/typescript.js +19 -0
  14. package/dist/configs/externals/base.d.ts +15 -0
  15. package/dist/configs/externals/base.js +16 -0
  16. package/dist/configs/externals/opinionated.d.ts +10 -0
  17. package/dist/configs/externals/opinionated.js +12 -0
  18. package/dist/index.d.ts +1170 -0
  19. package/dist/index.js +21 -0
  20. package/dist/plugin.d.ts +5 -0
  21. package/dist/plugin.js +14 -0
  22. package/dist/rules/no-boolean-coercion.d.ts +5 -0
  23. package/dist/rules/no-boolean-coercion.js +98 -0
  24. package/dist/rules/no-comments.d.ts +11 -0
  25. package/dist/rules/no-comments.js +83 -0
  26. package/dist/rules/no-default-export.d.ts +5 -0
  27. package/dist/rules/no-default-export.js +61 -0
  28. package/dist/rules/no-destructuring.d.ts +8 -0
  29. package/dist/rules/no-destructuring.js +121 -0
  30. package/dist/rules/prefer-is-empty.d.ts +5 -0
  31. package/dist/rules/prefer-is-empty.js +101 -0
  32. package/dist/rules/schemas-in-schemas-file.d.ts +9 -0
  33. package/dist/rules/schemas-in-schemas-file.js +141 -0
  34. package/dist/rules/top-level-functions.d.ts +5 -0
  35. package/dist/rules/top-level-functions.js +153 -0
  36. package/dist/rules/types-in-dts.d.ts +8 -0
  37. package/dist/rules/types-in-dts.js +76 -0
  38. package/package.json +25 -14
  39. package/.github/dependabot.yml +0 -15
  40. package/.github/workflows/codecov.yml +0 -26
  41. package/.github/workflows/codeql.yml +0 -82
  42. package/.github/workflows/dependency-review.yml +0 -20
  43. package/.github/workflows/main.yml +0 -43
  44. package/.github/workflows/scorecard.yml +0 -72
  45. package/.github/workflows/snyk-security.yml +0 -67
  46. package/.releaserc +0 -13
  47. package/.vscode/settings.json +0 -8
  48. package/.yarn/releases/yarn-4.12.0.cjs +0 -942
  49. package/.yarnrc.yml +0 -3
  50. package/CHANGELOG.md +0 -628
  51. package/SECURITY.md +0 -48
  52. package/docs/rules/no-boolean-coercion.md +0 -9
  53. package/docs/rules/no-comments.md +0 -50
  54. package/docs/rules/no-default-export.md +0 -26
  55. package/docs/rules/no-destructuring.md +0 -40
  56. package/docs/rules/prefer-is-empty.md +0 -9
  57. package/docs/rules/schemas-in-schemas-file.md +0 -170
  58. package/docs/rules/top-level-functions.md +0 -48
  59. package/docs/rules/types-in-dts.md +0 -112
  60. package/renovate.json +0 -3
  61. package/scripts/verify.mjs +0 -16
  62. package/src/index.js +0 -144
  63. package/src/rules/no-boolean-coercion.js +0 -124
  64. package/src/rules/no-comments.js +0 -94
  65. package/src/rules/no-default-export.js +0 -64
  66. package/src/rules/no-destructuring.js +0 -114
  67. package/src/rules/prefer-is-empty.js +0 -104
  68. package/src/rules/schemas-in-schemas-file.js +0 -191
  69. package/src/rules/top-level-functions.js +0 -200
  70. package/src/rules/types-in-dts.js +0 -94
  71. package/tests/no-boolean-coercion.test.ts +0 -83
  72. package/tests/prefer-is-empty.test.ts +0 -148
  73. package/tsconfig.json +0 -22
  74. package/xo.config.ts +0 -2
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { rules } from './plugin.js';
2
+ // Public bundles
3
+ import { recommended } from './configs/bundles/recommended.js';
4
+ import { recommendedReact } from './configs/bundles/recommended-react.js';
5
+ import { recommendedTypescript } from './configs/bundles/recommended-typescript.js';
6
+ // Internal layers (named exports only)
7
+ export { coreBase } from './configs/core/base.js';
8
+ export { coreTypescript } from './configs/core/typescript.js';
9
+ export { coreReact } from './configs/core/react.js';
10
+ export { externalsBase } from './configs/externals/base.js';
11
+ export { externalsOpinionated } from './configs/externals/opinionated.js';
12
+ export const configs = {
13
+ recommended,
14
+ 'recommended-react': recommendedReact,
15
+ 'recommended-typescript': recommendedTypescript,
16
+ };
17
+ export default {
18
+ rules,
19
+ configs,
20
+ };
21
+ export { rules } from './plugin.js';
@@ -0,0 +1,5 @@
1
+ export declare const rules: any;
2
+ export declare const plugin: {
3
+ rules: any;
4
+ };
5
+ export default plugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,14 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ const rulesDir = join(__dirname, 'rules');
7
+ const ruleFiles = readdirSync(rulesDir).filter(file => file.endsWith('.js'));
8
+ export const rules = Object.fromEntries(await Promise.all(ruleFiles.map(async (file) => {
9
+ const ruleName = file.replace('.js', '');
10
+ const ruleModule = await import(`./rules/${file}`);
11
+ return [ruleName, ruleModule.default];
12
+ })));
13
+ export const plugin = { rules };
14
+ export default plugin;
@@ -0,0 +1,5 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"useIsEmpty" | "useIsNil", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
@@ -0,0 +1,98 @@
1
+ import { ESLintUtils, } from '@typescript-eslint/utils';
2
+ const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-boolean-coercion.md');
3
+ export default createRule({
4
+ name: 'no-boolean-coercion',
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Disallow Boolean(value) or !!value. Enforce explicit checks: !_.isNil(value) for scalars and !_.isEmpty(value) for strings, arrays, and objects.',
9
+ },
10
+ hasSuggestions: true,
11
+ schema: [],
12
+ messages: {
13
+ useIsEmpty: 'Boolean coercion is not allowed. Use !_.isEmpty(value) for strings, arrays, and objects.',
14
+ useIsNil: 'Boolean coercion is not allowed. Use !_.isNil(value) for scalar values.',
15
+ },
16
+ },
17
+ defaultOptions: [],
18
+ create(context) {
19
+ const { sourceCode } = context;
20
+ const services = ESLintUtils.getParserServices(context);
21
+ const checker = services?.program?.getTypeChecker?.();
22
+ // --------------------------------------------------------------------
23
+ // Helpers
24
+ // --------------------------------------------------------------------
25
+ function isBooleanCall(node) {
26
+ return (node.type === 'CallExpression'
27
+ && node.callee.type === 'Identifier'
28
+ && node.callee.name === 'Boolean'
29
+ && node.arguments.length === 1);
30
+ }
31
+ function isDoubleNegation(node) {
32
+ return (node.type === 'UnaryExpression'
33
+ && node.operator === '!'
34
+ && node.argument.type === 'UnaryExpression'
35
+ && node.argument.operator === '!');
36
+ }
37
+ function isCollectionLikeByTS(node) {
38
+ if (!checker || !services.esTreeNodeToTSNodeMap) {
39
+ return false;
40
+ }
41
+ const tsNode = services.esTreeNodeToTSNodeMap.get(node);
42
+ if (!tsNode) {
43
+ return false;
44
+ }
45
+ const type = checker.getTypeAtLocation(tsNode);
46
+ const typeString = checker.typeToString(type);
47
+ return (typeString.includes('[]')
48
+ || typeString === 'string'
49
+ || typeString === 'object'
50
+ || typeString.startsWith('Array<')
51
+ || typeString.startsWith('ReadonlyArray<'));
52
+ }
53
+ function isCollectionLikeBySyntax(node) {
54
+ return (node.type === 'ArrayExpression'
55
+ || node.type === 'ObjectExpression'
56
+ || (node.type === 'Literal' && typeof node.value === 'string'));
57
+ }
58
+ function report(node, valueNode) {
59
+ const isCollection = isCollectionLikeBySyntax(valueNode)
60
+ || isCollectionLikeByTS(valueNode);
61
+ const suggestedFn = isCollection ? '_.isEmpty' : '_.isNil';
62
+ const replacement = `!${suggestedFn}(${sourceCode.getText(valueNode)})`;
63
+ context.report({
64
+ node,
65
+ messageId: isCollection ? 'useIsEmpty' : 'useIsNil',
66
+ suggest: [
67
+ {
68
+ messageId: isCollection ? 'useIsEmpty' : 'useIsNil',
69
+ fix(fixer) {
70
+ return fixer.replaceText(node, replacement);
71
+ },
72
+ },
73
+ ],
74
+ });
75
+ }
76
+ // --------------------------------------------------------------------
77
+ // Visitors
78
+ // --------------------------------------------------------------------
79
+ return {
80
+ CallExpression(node) {
81
+ if (isBooleanCall(node)) {
82
+ const arg = node.arguments[0];
83
+ if (arg) {
84
+ report(node, arg);
85
+ }
86
+ }
87
+ },
88
+ UnaryExpression(node) {
89
+ if (isDoubleNegation(node)) {
90
+ const valueNode = node.argument.argument;
91
+ if (valueNode) {
92
+ report(node, valueNode);
93
+ }
94
+ }
95
+ },
96
+ };
97
+ },
98
+ });
@@ -0,0 +1,11 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ allow?: string[];
5
+ disallow?: string[];
6
+ }?
7
+ ];
8
+ declare const _default: ESLintUtils.RuleModule<"commentNotAllowed", Options, unknown, ESLintUtils.RuleListener> & {
9
+ name: string;
10
+ };
11
+ export default _default;
@@ -0,0 +1,83 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ const DEFAULT_ALLOWED_PATTERNS = [
3
+ /todo/i, // Allow TODO (case-insensitive)
4
+ /warning/i, // Allow WARNING (case-insensitive)
5
+ /error/i, // Allow ERROR (case-insensitive)
6
+ /info/i, // Allow INFO (case-insensitive)
7
+ /^\s*eslint-(disable|enable|env|globals|ignore|directive)/,
8
+ ];
9
+ export default ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-comments.md')({
10
+ name: 'no-comments',
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description: 'Disallow comments except for specified allowed patterns.',
15
+ },
16
+ fixable: 'code',
17
+ schema: [
18
+ {
19
+ type: 'object',
20
+ properties: {
21
+ allow: {
22
+ type: 'array',
23
+ items: { type: 'string' },
24
+ description: 'Additional patterns to allow in comments.',
25
+ },
26
+ disallow: {
27
+ type: 'array',
28
+ items: { type: 'string' },
29
+ description: 'Additional patterns to disallow in comments.',
30
+ },
31
+ },
32
+ additionalProperties: false,
33
+ },
34
+ ],
35
+ messages: {
36
+ commentNotAllowed: 'Comment not allowed.',
37
+ },
38
+ },
39
+ defaultOptions: [],
40
+ create(context) {
41
+ const option = context.options[0] ?? {};
42
+ const userAllowedPatterns = (option.allow ?? []).map(pattern => new RegExp(pattern));
43
+ const userDisallowedPatterns = (option.disallow ?? []).map(pattern => new RegExp(pattern));
44
+ function isCommentAllowed(comment) {
45
+ const text = comment.value.trim();
46
+ // Allow JSDoc
47
+ if (comment.type === 'Block' && comment.value.startsWith('*')) {
48
+ return true;
49
+ }
50
+ for (const pattern of [
51
+ ...DEFAULT_ALLOWED_PATTERNS,
52
+ ...userAllowedPatterns,
53
+ ]) {
54
+ if (pattern.test(text)) {
55
+ return true;
56
+ }
57
+ }
58
+ for (const pattern of userDisallowedPatterns) {
59
+ if (pattern.test(text)) {
60
+ return false;
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+ return {
66
+ Program() {
67
+ const sourceCode = context.getSourceCode();
68
+ const comments = sourceCode.getAllComments();
69
+ for (const comment of comments) {
70
+ if (!isCommentAllowed(comment)) {
71
+ context.report({
72
+ node: comment,
73
+ messageId: 'commentNotAllowed',
74
+ fix(fixer) {
75
+ return fixer.remove(comment);
76
+ },
77
+ });
78
+ }
79
+ }
80
+ },
81
+ };
82
+ },
83
+ });
@@ -0,0 +1,5 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"unnamed", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
@@ -0,0 +1,61 @@
1
+ import * as path from 'node:path';
2
+ import { ESLintUtils } from '@typescript-eslint/utils';
3
+ export default ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-default-export.md')({
4
+ name: 'no-default-export',
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Convert unnamed default exports to named default exports based on the file name.',
9
+ },
10
+ fixable: 'code',
11
+ schema: [],
12
+ messages: {
13
+ unnamed: 'Unnamed default export should be named based on the file name.',
14
+ },
15
+ },
16
+ defaultOptions: [],
17
+ create(context) {
18
+ function generateExportNameFromFileName(fileName) {
19
+ // Remove all invalid characters, replace with spaces
20
+ const cleaned = fileName.replaceAll(/[^a-zA-Z\d]+/g, ' ');
21
+ // Split into tokens
22
+ const parts = cleaned
23
+ .trim()
24
+ .split(/\s+/g)
25
+ .filter(Boolean);
26
+ if (parts.length === 0) {
27
+ return 'defaultExport';
28
+ }
29
+ // Build camelCase
30
+ const [first, ...rest] = parts;
31
+ return (first.charAt(0).toLowerCase() + first.slice(1)) + rest
32
+ .map(p => p.charAt(0).toUpperCase() + p.slice(1))
33
+ .join('');
34
+ }
35
+ return {
36
+ ExportDefaultDeclaration(node) {
37
+ // 1. skip `export default Foo`
38
+ if (node.declaration.type === 'Identifier') {
39
+ return;
40
+ }
41
+ // 2. skip named function/class: `export default function Foo() {}`
42
+ if ('id' in node.declaration && node.declaration.id != null) {
43
+ return;
44
+ }
45
+ const fileName = context.getFilename();
46
+ const base = path.basename(fileName, path.extname(fileName));
47
+ const exportName = generateExportNameFromFileName(base);
48
+ context.report({
49
+ node,
50
+ messageId: 'unnamed',
51
+ fix(fixer) {
52
+ const sourceCode = context.getSourceCode();
53
+ const declText = sourceCode.getText(node.declaration);
54
+ const replacement = `const ${exportName} = ${declText};\nexport default ${exportName};`;
55
+ return fixer.replaceText(node, replacement);
56
+ },
57
+ });
58
+ },
59
+ };
60
+ },
61
+ });
@@ -0,0 +1,8 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
3
+ maximumDestructuredVariables: number;
4
+ maximumLineLength: number;
5
+ }], unknown, ESLintUtils.RuleListener> & {
6
+ name: string;
7
+ };
8
+ export default _default;
@@ -0,0 +1,121 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ const MAX_TAB_COUNT = 3;
3
+ export default ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-destructuring.md')({
4
+ name: 'no-destructuring',
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Disallow destructuring that does not meet certain conditions.',
9
+ },
10
+ schema: [
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ maximumDestructuredVariables: { type: 'integer', minimum: 0 },
15
+ maximumLineLength: { type: 'integer', minimum: 0 },
16
+ },
17
+ additionalProperties: false,
18
+ },
19
+ ],
20
+ messages: {
21
+ tooDeep: 'Destructuring at a nesting level above {{max}} is not allowed; found {{actual}} levels of nesting.',
22
+ tooMany: 'Destructuring of more than {{max}} variables is not allowed.',
23
+ tooLong: 'Destructuring spanning a line exceeding {{max}} characters is not allowed.',
24
+ },
25
+ },
26
+ defaultOptions: [
27
+ {
28
+ maximumDestructuredVariables: 2,
29
+ maximumLineLength: 100,
30
+ },
31
+ ],
32
+ create(context, [options]) {
33
+ const MAX_VARIABLES = options.maximumDestructuredVariables ?? 2;
34
+ const MAX_LINE_LENGTH = options.maximumLineLength ?? 100;
35
+ const sourceCode = context.getSourceCode();
36
+ function reportIfNeeded(patternNode, reportNode = patternNode) {
37
+ if (patternNode?.type !== 'ObjectPattern'
38
+ || !patternNode.loc) {
39
+ return;
40
+ }
41
+ const startLine = patternNode.loc.start.line;
42
+ const endLine = patternNode.loc.end.line;
43
+ const lineText = sourceCode.lines[startLine - 1] ?? '';
44
+ const indentCount = lineText.search(/\S|$/);
45
+ const propertyCount = (patternNode).properties
46
+ ?.length ?? 0;
47
+ let maxSpannedLineLength = 0;
48
+ for (let i = startLine; i <= endLine; i++) {
49
+ const t = sourceCode.lines[i - 1] ?? '';
50
+ if (t.length > maxSpannedLineLength) {
51
+ maxSpannedLineLength = t.length;
52
+ }
53
+ }
54
+ if (indentCount > MAX_TAB_COUNT) {
55
+ context.report({
56
+ node: reportNode,
57
+ messageId: 'tooDeep',
58
+ data: {
59
+ max: MAX_TAB_COUNT,
60
+ actual: indentCount,
61
+ },
62
+ });
63
+ }
64
+ if (propertyCount > MAX_VARIABLES) {
65
+ context.report({
66
+ node: reportNode,
67
+ messageId: 'tooMany',
68
+ data: {
69
+ max: MAX_VARIABLES,
70
+ },
71
+ });
72
+ }
73
+ if (maxSpannedLineLength > MAX_LINE_LENGTH) {
74
+ context.report({
75
+ node: reportNode,
76
+ messageId: 'tooLong',
77
+ data: {
78
+ max: MAX_LINE_LENGTH,
79
+ },
80
+ });
81
+ }
82
+ }
83
+ function checkParameters(parameters) {
84
+ for (const p of parameters || []) {
85
+ if (!p) {
86
+ continue;
87
+ }
88
+ // (...args = {}) pattern
89
+ if (p.type === 'AssignmentPattern') {
90
+ reportIfNeeded(p.left, p);
91
+ continue;
92
+ }
93
+ reportIfNeeded(p, p);
94
+ }
95
+ }
96
+ return {
97
+ VariableDeclarator(node) {
98
+ reportIfNeeded(node.id, node);
99
+ },
100
+ FunctionDeclaration(node) {
101
+ checkParameters(node.params);
102
+ },
103
+ FunctionExpression(node) {
104
+ checkParameters(node.params);
105
+ },
106
+ ArrowFunctionExpression(node) {
107
+ checkParameters(node.params);
108
+ },
109
+ MethodDefinition(node) {
110
+ if (node.value?.params) {
111
+ checkParameters(node.value.params);
112
+ }
113
+ },
114
+ TSDeclareFunction(node) {
115
+ if (node.params) {
116
+ checkParameters(node.params);
117
+ }
118
+ },
119
+ };
120
+ },
121
+ });
@@ -0,0 +1,5 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ declare const _default: ESLintUtils.RuleModule<"useIsEmpty", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
@@ -0,0 +1,101 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export default ESLintUtils.RuleCreator(() => "https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/prefer-is-empty.md")({
3
+ name: "prefer-is-empty",
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Require _.isEmpty instead of length comparisons."
8
+ },
9
+ hasSuggestions: true,
10
+ schema: [],
11
+ messages: {
12
+ useIsEmpty: "Use _.isEmpty({{collection}}) instead of checking {{collection}}.length {{operator}} {{value}}."
13
+ }
14
+ },
15
+ defaultOptions: [],
16
+ create(context) {
17
+ const sourceCode = context.getSourceCode();
18
+ function isLengthAccess(node) {
19
+ return (!!node &&
20
+ node.type === "MemberExpression" &&
21
+ node.property.type === "Identifier" &&
22
+ node.property.name === "length" &&
23
+ node.computed === false);
24
+ }
25
+ function isNumericLiteral(node) {
26
+ return (!!node &&
27
+ node.type === "Literal" &&
28
+ typeof node.value === "number");
29
+ }
30
+ function report(node, collectionNode, operator, value, isEmptyCheck) {
31
+ const collectionText = sourceCode.getText(collectionNode.object);
32
+ const replacement = isEmptyCheck
33
+ ? `_.isEmpty(${collectionText})`
34
+ : `!_.isEmpty(${collectionText})`;
35
+ context.report({
36
+ node,
37
+ messageId: "useIsEmpty",
38
+ data: {
39
+ collection: collectionText,
40
+ operator,
41
+ value
42
+ },
43
+ suggest: [
44
+ {
45
+ messageId: "useIsEmpty",
46
+ data: {
47
+ collection: collectionText,
48
+ operator,
49
+ value
50
+ },
51
+ fix(fixer) {
52
+ return fixer.replaceText(node, replacement);
53
+ }
54
+ }
55
+ ]
56
+ });
57
+ }
58
+ return {
59
+ BinaryExpression(node) {
60
+ const { left, right, operator } = node;
61
+ //
62
+ // Case 1: values.length <op> N
63
+ //
64
+ if (isLengthAccess(left) && isNumericLiteral(right)) {
65
+ const value = right.value;
66
+ // EMPTY checks
67
+ if ((operator === "===" && value === 0) ||
68
+ (operator === "<=" && value === 0) ||
69
+ (operator === "<" && value === 1)) {
70
+ report(node, left, operator, value, true);
71
+ return;
72
+ }
73
+ // NOT EMPTY checks
74
+ if ((operator === ">" && value === 0) ||
75
+ (operator === ">=" && value === 1) ||
76
+ ((operator === "!=" || operator === "!==") && value === 0)) {
77
+ report(node, left, operator, value, false);
78
+ }
79
+ }
80
+ //
81
+ // Case 2: N <op> values.length (reverse order)
82
+ //
83
+ if (isNumericLiteral(left) && isLengthAccess(right)) {
84
+ const value = left.value;
85
+ // EMPTY checks
86
+ if ((operator === "===" && value === 0) ||
87
+ (operator === ">=" && value === 0) ||
88
+ (operator === ">" && value === 0)) {
89
+ report(node, right, operator, value, true);
90
+ return;
91
+ }
92
+ // NOT EMPTY checks
93
+ if ((operator === "<" && value === 1) ||
94
+ (operator === "<=" && value === 0)) {
95
+ report(node, right, operator, value, false);
96
+ }
97
+ }
98
+ }
99
+ };
100
+ }
101
+ });
@@ -0,0 +1,9 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"moveSchema", [{
3
+ allowedSuffixes: string[];
4
+ onlyWhenAssigned: boolean;
5
+ allowInTests: boolean;
6
+ }], unknown, ESLintUtils.RuleListener> & {
7
+ name: string;
8
+ };
9
+ export default _default;