eslint-plugin-th-rules 3.1.8 → 3.2.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AAQhD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,eAAO,MAAM,OAAO;;;;;CAKnB,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE1E,QAAA,MAAM,KAAK,EAAoC;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AACpI,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AAQhD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,eAAO,MAAM,OAAO;;;;;CAKnB,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE1E,QAAA,MAAM,KAAK,EAAoC;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AACpI,eAAe,KAAK,CAAC"}
package/dist/plugin.d.ts CHANGED
@@ -1,81 +1,87 @@
1
1
  export declare const rules: {
2
- noBooleanCoercion: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
2
+ 'no-boolean-coercion': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
3
3
  name: string;
4
4
  };
5
- noComments: import("@typescript-eslint/utils/ts-eslint").RuleModule<"commentNotAllowed", [({
5
+ 'no-comments': import("@typescript-eslint/utils/ts-eslint").RuleModule<"commentNotAllowed", [({
6
6
  allow?: string[];
7
7
  disallow?: string[];
8
8
  } | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
9
9
  name: string;
10
10
  };
11
- noDefaultExport: import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnamed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
11
+ 'no-default-export': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnamed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
12
12
  name: string;
13
13
  };
14
- noDestructuring: import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
14
+ 'no-destructuring': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
15
15
  maximumDestructuredVariables: number;
16
16
  maximumLineLength: number;
17
17
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
18
18
  name: string;
19
19
  };
20
- preferIsEmpty: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
20
+ 'prefer-is-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
21
21
  name: string;
22
22
  };
23
- schemasInSchemasFile: import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveSchema", [{
23
+ 'schemas-in-schemas-file': import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveSchema", [{
24
24
  allowedSuffixes: string[];
25
25
  onlyWhenAssigned: boolean;
26
26
  allowInTests: boolean;
27
27
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
28
28
  name: string;
29
29
  };
30
- topLevelFunctions: import("@typescript-eslint/utils/ts-eslint").RuleModule<"arrow" | "funcExpr" | "anonDecl", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
30
+ 'top-level-functions': import("@typescript-eslint/utils/ts-eslint").RuleModule<"arrow" | "funcExpr" | "anonDecl", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
31
31
  name: string;
32
32
  };
33
- typesInDts: import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveToDts", [{
33
+ 'types-in-dts': import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveToDts", [{
34
34
  allowEnums: boolean;
35
35
  allowDeclare: boolean;
36
36
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
37
37
  name: string;
38
38
  };
39
+ 'no-explicit-nil-compare': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNull" | "useIsUndefined", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
40
+ name: string;
41
+ };
39
42
  };
40
43
  declare const plugin: {
41
44
  rules: {
42
- noBooleanCoercion: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
45
+ 'no-boolean-coercion': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
43
46
  name: string;
44
47
  };
45
- noComments: import("@typescript-eslint/utils/ts-eslint").RuleModule<"commentNotAllowed", [({
48
+ 'no-comments': import("@typescript-eslint/utils/ts-eslint").RuleModule<"commentNotAllowed", [({
46
49
  allow?: string[];
47
50
  disallow?: string[];
48
51
  } | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
49
52
  name: string;
50
53
  };
51
- noDefaultExport: import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnamed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
54
+ 'no-default-export': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnamed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
52
55
  name: string;
53
56
  };
54
- noDestructuring: import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
57
+ 'no-destructuring': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
55
58
  maximumDestructuredVariables: number;
56
59
  maximumLineLength: number;
57
60
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
58
61
  name: string;
59
62
  };
60
- preferIsEmpty: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
63
+ 'prefer-is-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
61
64
  name: string;
62
65
  };
63
- schemasInSchemasFile: import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveSchema", [{
66
+ 'schemas-in-schemas-file': import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveSchema", [{
64
67
  allowedSuffixes: string[];
65
68
  onlyWhenAssigned: boolean;
66
69
  allowInTests: boolean;
67
70
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
68
71
  name: string;
69
72
  };
70
- topLevelFunctions: import("@typescript-eslint/utils/ts-eslint").RuleModule<"arrow" | "funcExpr" | "anonDecl", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
73
+ 'top-level-functions': import("@typescript-eslint/utils/ts-eslint").RuleModule<"arrow" | "funcExpr" | "anonDecl", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
71
74
  name: string;
72
75
  };
73
- typesInDts: import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveToDts", [{
76
+ 'types-in-dts': import("@typescript-eslint/utils/ts-eslint").RuleModule<"moveToDts", [{
74
77
  allowEnums: boolean;
75
78
  allowDeclare: boolean;
76
79
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
77
80
  name: string;
78
81
  };
82
+ 'no-explicit-nil-compare': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNull" | "useIsUndefined", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
83
+ name: string;
84
+ };
79
85
  };
80
86
  };
81
87
  export default plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CASjB,CAAC;AAEF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAY,CAAC;AACzB,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUjB,CAAC;AAEF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAY,CAAC;AACzB,eAAe,MAAM,CAAC"}
package/dist/plugin.js CHANGED
@@ -2,19 +2,21 @@ import noBooleanCoercion from './rules/no-boolean-coercion.js';
2
2
  import noComments from './rules/no-comments.js';
3
3
  import noDefaultExport from './rules/no-default-export.js';
4
4
  import noDestructuring from './rules/no-destructuring.js';
5
+ import noExplicitNilCompare from './rules/no-explicit-nil-compare.js';
5
6
  import preferIsEmpty from './rules/prefer-is-empty.js';
6
7
  import schemasInSchemasFile from './rules/schemas-in-schemas-file.js';
7
8
  import topLevelFunctions from './rules/top-level-functions.js';
8
9
  import typesInDts from './rules/types-in-dts.js';
9
10
  export const rules = {
10
- noBooleanCoercion,
11
- noComments,
12
- noDefaultExport,
13
- noDestructuring,
14
- preferIsEmpty,
15
- schemasInSchemasFile,
16
- topLevelFunctions,
17
- typesInDts,
11
+ 'no-boolean-coercion': noBooleanCoercion,
12
+ 'no-comments': noComments,
13
+ 'no-default-export': noDefaultExport,
14
+ 'no-destructuring': noDestructuring,
15
+ 'prefer-is-empty': preferIsEmpty,
16
+ 'schemas-in-schemas-file': schemasInSchemasFile,
17
+ 'top-level-functions': topLevelFunctions,
18
+ 'types-in-dts': typesInDts,
19
+ 'no-explicit-nil-compare': noExplicitNilCompare,
18
20
  };
19
21
  const plugin = { rules };
20
22
  export default plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"no-boolean-coercion.d.ts","sourceRoot":"","sources":["../../src/rules/no-boolean-coercion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItE,QAAA,MAAM,iBAAiB;;CAwFrB,CAAC;AACH,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"no-boolean-coercion.d.ts","sourceRoot":"","sources":["../../src/rules/no-boolean-coercion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAQtF,QAAA,MAAM,iBAAiB;;CAmGrB,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
@@ -1,13 +1,18 @@
1
- import { ESLintUtils } from '@typescript-eslint/utils';
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+ /* eslint-disable new-cap */
3
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
+ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
2
5
  const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-boolean-coercion.md');
6
+ const LODASH_MODULE = 'lodash';
7
+ const LODASH_IDENT = '_';
3
8
  const noBooleanCoercion = createRule({
4
9
  name: 'no-boolean-coercion',
5
10
  meta: {
6
11
  type: 'problem',
12
+ fixable: 'code',
7
13
  docs: {
8
14
  description: 'Disallow Boolean(value) or !!value. Enforce explicit checks: !_.isNil(value) for scalars and !_.isEmpty(value) for strings, arrays, and objects.',
9
15
  },
10
- hasSuggestions: true,
11
16
  schema: [],
12
17
  messages: {
13
18
  useIsEmpty: 'Boolean coercion is not allowed. Use !_.isEmpty(value) for strings, arrays, and objects.',
@@ -16,62 +21,67 @@ const noBooleanCoercion = createRule({
16
21
  },
17
22
  defaultOptions: [],
18
23
  create(context) {
19
- const { sourceCode } = context;
20
24
  const services = ESLintUtils.getParserServices(context);
21
- const checker = services?.program?.getTypeChecker?.();
25
+ const checker = services.program.getTypeChecker();
26
+ function hasLodashImport() {
27
+ return context.sourceCode.ast.body.some((node) => node.type === AST_NODE_TYPES.ImportDeclaration && node.source.value === LODASH_MODULE);
28
+ }
29
+ function getLodashImportFixer(fixer) {
30
+ if (hasLodashImport()) {
31
+ return null;
32
+ }
33
+ const firstNode = context.sourceCode.ast.body[0];
34
+ const importText = `import ${LODASH_IDENT} from '${LODASH_MODULE}';\n`;
35
+ if (!firstNode) {
36
+ return fixer.insertTextAfterRange([0, 0], importText);
37
+ }
38
+ return fixer.insertTextBefore(firstNode, importText);
39
+ }
22
40
  function isBooleanCall(node) {
23
- return node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'Boolean' && node.arguments.length === 1;
41
+ return node.type === AST_NODE_TYPES.CallExpression && node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === 'Boolean' && node.arguments.length === 1;
24
42
  }
25
43
  function isDoubleNegation(node) {
26
- return node.type === 'UnaryExpression' && node.operator === '!' && node.argument.type === 'UnaryExpression' && node.argument.operator === '!';
44
+ return node.type === AST_NODE_TYPES.UnaryExpression && node.operator === '!' && node.argument.type === AST_NODE_TYPES.UnaryExpression && node.argument.operator === '!';
27
45
  }
28
46
  function isCollectionLikeByTS(node) {
29
- if (!checker || !services.esTreeNodeToTSNodeMap) {
30
- return false;
31
- }
32
47
  const tsNode = services.esTreeNodeToTSNodeMap.get(node);
33
48
  if (!tsNode) {
34
49
  return false;
35
50
  }
36
51
  const type = checker.getTypeAtLocation(tsNode);
37
52
  const typeString = checker.typeToString(type);
38
- return typeString.includes('[]') || typeString === 'string' || typeString === 'object' || typeString.startsWith('Array<') || typeString.startsWith('ReadonlyArray<');
53
+ return typeString === 'string' || typeString === 'object' || typeString.includes('[]') || typeString.startsWith('Array<') || typeString.startsWith('ReadonlyArray<');
39
54
  }
40
55
  function isCollectionLikeBySyntax(node) {
41
- return node.type === 'ArrayExpression' || node.type === 'ObjectExpression' || (node.type === 'Literal' && typeof node.value === 'string');
56
+ return node.type === AST_NODE_TYPES.ArrayExpression || node.type === AST_NODE_TYPES.ObjectExpression || (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string');
42
57
  }
43
58
  function report(node, valueNode) {
44
59
  const isCollection = isCollectionLikeBySyntax(valueNode) || isCollectionLikeByTS(valueNode);
45
- const suggestedFn = isCollection ? '_.isEmpty' : '_.isNil';
46
- const replacement = `!${suggestedFn}(${sourceCode.getText(valueNode)})`;
60
+ const fnName = isCollection ? 'isEmpty' : 'isNil';
61
+ const replacement = `!${LODASH_IDENT}.${fnName}(${context.sourceCode.getText(valueNode)})`;
47
62
  context.report({
48
63
  node,
49
64
  messageId: isCollection ? 'useIsEmpty' : 'useIsNil',
50
- suggest: [
51
- {
52
- messageId: isCollection ? 'useIsEmpty' : 'useIsNil',
53
- fix(fixer) {
54
- return fixer.replaceText(node, replacement);
55
- },
56
- },
57
- ],
65
+ fix(fixer) {
66
+ const fixes = [fixer.replaceText(node, replacement)];
67
+ const importFix = getLodashImportFixer(fixer);
68
+ if (importFix) {
69
+ fixes.push(importFix);
70
+ }
71
+ return fixes;
72
+ },
58
73
  });
59
74
  }
60
75
  return {
61
76
  CallExpression(node) {
62
77
  if (isBooleanCall(node)) {
63
- const arg = node.arguments[0];
64
- if (arg) {
65
- report(node, arg);
66
- }
78
+ report(node, node.arguments[0]);
67
79
  }
68
80
  },
69
81
  UnaryExpression(node) {
70
82
  if (isDoubleNegation(node)) {
71
83
  const valueNode = node.argument.argument;
72
- if (valueNode) {
73
- report(node, valueNode);
74
- }
84
+ report(node, valueNode);
75
85
  }
76
86
  },
77
87
  };
@@ -1 +1 @@
1
- {"version":3,"file":"no-destructuring.d.ts","sourceRoot":"","sources":["../../src/rules/no-destructuring.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItE,QAAA,MAAM,eAAe;;;;;CA2InB,CAAC;AACH,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"no-destructuring.d.ts","sourceRoot":"","sources":["../../src/rules/no-destructuring.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItF,QAAA,MAAM,eAAe;;;;;CAyInB,CAAC;AACH,eAAe,eAAe,CAAC"}
@@ -1,4 +1,6 @@
1
- import { ESLintUtils } from '@typescript-eslint/utils';
1
+ /* eslint-disable new-cap */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
+ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
2
4
  const MAX_TAB_COUNT = 3;
3
5
  const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-destructuring.md')({
4
6
  name: 'no-destructuring',
@@ -32,19 +34,18 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
32
34
  create(context, [options]) {
33
35
  const MAX_VARIABLES = options.maximumDestructuredVariables ?? 2;
34
36
  const MAX_LINE_LENGTH = options.maximumLineLength ?? 100;
35
- const sourceCode = context.getSourceCode();
36
37
  function reportIfNeeded(patternNode, reportNode = patternNode) {
37
- if (patternNode?.type !== 'ObjectPattern' || !patternNode.loc) {
38
+ if (patternNode?.type !== AST_NODE_TYPES.ObjectPattern || !patternNode.loc) {
38
39
  return;
39
40
  }
40
41
  const startLine = patternNode.loc.start.line;
41
42
  const endLine = patternNode.loc.end.line;
42
- const lineText = sourceCode.lines[startLine - 1] ?? '';
43
+ const lineText = context.sourceCode.lines[startLine - 1] ?? '';
43
44
  const indentCount = lineText.search(/\S|$/);
44
45
  const propertyCount = patternNode.properties?.length ?? 0;
45
46
  let maxSpannedLineLength = 0;
46
47
  for (let i = startLine; i <= endLine; i++) {
47
- const t = sourceCode.lines[i - 1] ?? '';
48
+ const t = context.sourceCode.lines[i - 1] ?? '';
48
49
  if (t.length > maxSpannedLineLength) {
49
50
  maxSpannedLineLength = t.length;
50
51
  }
@@ -83,7 +84,7 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
83
84
  if (!p) {
84
85
  continue;
85
86
  }
86
- if (p.type === 'AssignmentPattern') {
87
+ if (p.type === AST_NODE_TYPES.AssignmentPattern) {
87
88
  reportIfNeeded(p.left, p);
88
89
  continue;
89
90
  }
@@ -0,0 +1,6 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const noExplicitNilCompare: ESLintUtils.RuleModule<"useIsNull" | "useIsUndefined", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default noExplicitNilCompare;
6
+ //# sourceMappingURL=no-explicit-nil-compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-explicit-nil-compare.d.ts","sourceRoot":"","sources":["../../src/rules/no-explicit-nil-compare.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItF,QAAA,MAAM,oBAAoB;;CAmFxB,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,82 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ /* eslint-disable new-cap */
4
+ import _ from 'lodash';
5
+ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
6
+ const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-explicit-nil-compare.md');
7
+ const noExplicitNilCompare = createRule({
8
+ name: 'no-explicit-nil-compare',
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Disallow direct comparisons to null or undefined. Use _.isNull(x) / _.isUndefined(x) instead.',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ useIsNull: 'Use _.isNull({{value}}) instead of comparing directly to null.',
18
+ useIsUndefined: 'Use _.isUndefined({{value}}) instead of comparing directly to undefined.',
19
+ },
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ /** Ensure lodash default import exists */
24
+ function ensureLodashImport(fixer) {
25
+ const existingImport = context.sourceCode.ast.body.find((node) => node.type === AST_NODE_TYPES.ImportDeclaration && node.source.value === 'lodash');
26
+ if (existingImport)
27
+ return null;
28
+ return fixer.insertTextBeforeRange([0, 0], `import _ from 'lodash';\n`);
29
+ }
30
+ function isNullLiteral(node) {
31
+ return node.type === AST_NODE_TYPES.Literal && _.isNull(node.value);
32
+ }
33
+ function isUndefinedIdentifier(node) {
34
+ return node.type === AST_NODE_TYPES.Identifier && node.name === 'undefined';
35
+ }
36
+ function reportComparison(node, left, right) {
37
+ let targetNode;
38
+ let isNull = false;
39
+ const isNegated = node.operator === '!=' || node.operator === '!==';
40
+ if (isNullLiteral(right)) {
41
+ targetNode = left;
42
+ isNull = true;
43
+ }
44
+ else if (isNullLiteral(left)) {
45
+ targetNode = right;
46
+ isNull = true;
47
+ }
48
+ else if (isUndefinedIdentifier(right)) {
49
+ targetNode = left;
50
+ }
51
+ else if (isUndefinedIdentifier(left)) {
52
+ targetNode = right;
53
+ }
54
+ if (!targetNode)
55
+ return;
56
+ const text = context.sourceCode.getText(targetNode);
57
+ const positive = isNull ? `_.isNull(${text})` : `_.isUndefined(${text})`;
58
+ const replacement = isNegated ? `!${positive}` : positive;
59
+ context.report({
60
+ node,
61
+ messageId: isNull ? 'useIsNull' : 'useIsUndefined',
62
+ data: { value: text },
63
+ fix(fixer) {
64
+ const fixes = [];
65
+ const importFix = ensureLodashImport(fixer);
66
+ if (importFix)
67
+ fixes.push(importFix);
68
+ fixes.push(fixer.replaceText(node, replacement));
69
+ return fixes;
70
+ },
71
+ });
72
+ }
73
+ return {
74
+ BinaryExpression(node) {
75
+ if (!['==', '===', '!=', '!=='].includes(node.operator))
76
+ return;
77
+ reportComparison(node, node.left, node.right);
78
+ },
79
+ };
80
+ },
81
+ });
82
+ export default noExplicitNilCompare;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-th-rules",
3
- "version": "3.1.8",
3
+ "version": "3.2.1",
4
4
  "description": "A List of custom ESLint rules created by Tomer Horowitz",
5
5
  "keywords": [
6
6
  "eslint",