eslint-plugin-ziloen 0.1.20 → 0.1.22

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/dist/index.d.ts CHANGED
@@ -1,6 +1,3 @@
1
1
  import { ESLint } from "eslint";
2
-
3
- //#region src/index.d.ts
4
2
  declare const _default: ESLint.Plugin;
5
- //#endregion
6
3
  export { _default as default };
package/dist/index.js CHANGED
@@ -1,11 +1,10 @@
1
- import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
2
- import { TypeFlags } from "typescript";
1
+ import { ASTUtils, AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
2
+ import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
3
3
 
4
- //#region src/utils.ts
4
+ // src/utils.ts
5
5
  const createEslintRule = ESLintUtils.RuleCreator((ruleName) => ruleName);
6
6
 
7
- //#endregion
8
- //#region src/rules/generic-spacing.ts
7
+ // src/rules/generic-spacing.ts
9
8
  const RULE_NAME$1 = "generic-spacing";
10
9
  const messageId = "genericSpacingMismatch";
11
10
  function removeSpaceAround(node, context) {
@@ -85,31 +84,14 @@ const genericSpacing = createEslintRule({
85
84
  }
86
85
  });
87
86
 
88
- //#endregion
89
- //#region src/tsutils.ts
90
- function isUnionType(type) {
91
- return (type.flags & TypeFlags.Union) !== 0;
92
- }
93
- /** Returns all types of a union type or an array containing `type` itself if it's no union type. */
94
- function unionTypeParts(type) {
95
- return isUnionType(type) ? type.types : [type];
96
- }
97
- function isTypeFlagSet(type, flag) {
98
- return (type.flags & flag) !== 0;
99
- }
100
- /**
101
- * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one.
102
- */
103
- function getConstrainedTypeAtLocation(checker, node) {
104
- const nodeType = checker.getTypeAtLocation(node);
105
- return checker.getBaseConstraintOfType(nodeType) ?? nodeType;
106
- }
107
-
108
- //#endregion
109
- //#region src/rules/jsx-strict-logical-expressions.ts
87
+ // src/rules/jsx-strict-logical-expressions.ts
110
88
  /**
111
89
  * https://github.com/hluisson/eslint-plugin-jsx-expressions
112
90
  */
91
+ const messages = {
92
+ conditionErrorFalseyString: "Potentially falsy string in logical AND expression. Please use a boolean.",
93
+ conditionErrorFalseyNumber: "Potentially falsy number or bigint in logical AND expression. Please use a boolean."
94
+ };
113
95
  const RULE_NAME = "jsx-strict-logical-expressions";
114
96
  const jsxStrictLogicalExpressions = createEslintRule({
115
97
  name: RULE_NAME,
@@ -118,9 +100,9 @@ const jsxStrictLogicalExpressions = createEslintRule({
118
100
  allowNumber: false
119
101
  }],
120
102
  meta: {
121
- docs: { description: "Forbid non-boolean falsey values in inline expressions" },
122
- fixable: "code",
123
103
  type: "problem",
104
+ fixable: "code",
105
+ docs: { description: "Forbid non-boolean falsy values in JSX logical AND expressions" },
124
106
  schema: [{
125
107
  type: "object",
126
108
  properties: {
@@ -129,57 +111,102 @@ const jsxStrictLogicalExpressions = createEslintRule({
129
111
  },
130
112
  additionalProperties: false
131
113
  }],
132
- messages: {
133
- conditionErrorFalseyString: "Potentially falsey string in logical AND expression. Please use boolean.",
134
- conditionErrorFalseyNumber: "Potentially falsey number in logical AND expression. Please use boolean."
135
- }
114
+ messages
136
115
  },
137
- create(context, [options]) {
138
- const parserServices = ESLintUtils.getParserServices(context);
139
- const typeChecker = parserServices.program.getTypeChecker();
140
- function checkIdentifier(node) {
141
- const types = unionTypeParts(getConstrainedTypeAtLocation(typeChecker, parserServices.esTreeNodeToTSNodeMap.get(node)));
142
- const hasPotentiallyFalseyString = types.some((type) => isTypeFlagSet(type, TypeFlags.StringLike) && (!type.isStringLiteral() || type.value === ""));
143
- if (!options.allowString && hasPotentiallyFalseyString) return "conditionErrorFalseyString";
144
- const hasPotentiallyFalseyNumber = types.some((type) => isTypeFlagSet(type, TypeFlags.NumberLike | TypeFlags.BigIntLike) && (!type.isNumberLiteral() || type.value === 0));
145
- if (!options.allowNumber && hasPotentiallyFalseyNumber) return "conditionErrorFalseyNumber";
146
- }
147
- function checkAndReportIdentifier(node, fixNode) {
148
- const errorId = checkIdentifier(node);
149
- if (errorId) context.report({
150
- node,
151
- messageId: errorId,
152
- fix: (fixer) => fixer.insertTextBefore(fixNode, "!!")
153
- });
154
- }
155
- function determineNode(originalNode) {
156
- let nodeToEvaluate = originalNode;
157
- if (nodeToEvaluate.type === TSESTree.AST_NODE_TYPES.ChainExpression) nodeToEvaluate = nodeToEvaluate.expression;
158
- if (nodeToEvaluate.type === TSESTree.AST_NODE_TYPES.MemberExpression && nodeToEvaluate.property.type !== TSESTree.AST_NODE_TYPES.PrivateIdentifier) nodeToEvaluate = nodeToEvaluate.property;
159
- return nodeToEvaluate;
116
+ create(context, [{ allowString = false, allowNumber = false }]) {
117
+ const services = ESLintUtils.getParserServices(context);
118
+ const checker = services.program.getTypeChecker();
119
+ const stringType = checker.getStringType();
120
+ const numberType = checker.getNumberType();
121
+ const bigintType = checker.getBigIntType();
122
+ /**
123
+ * Returns true when `value` is TypeScript's internal representation
124
+ * of a non-zero bigint literal.
125
+ */
126
+ function isNonZeroBigIntLiteralValue(value) {
127
+ return typeof value === "object" && value !== null && "base10Value" in value && typeof value.base10Value === "string" && value.base10Value !== "0";
160
128
  }
161
- function checkLogicalExpression(expressionNode, checkRightNode) {
162
- const leftNode = determineNode(expressionNode.left);
163
- if (leftNode.type === TSESTree.AST_NODE_TYPES.LogicalExpression) checkLogicalExpression(leftNode, true);
164
- else if (leftNode.type === TSESTree.AST_NODE_TYPES.Identifier) checkAndReportIdentifier(leftNode, expressionNode.left);
165
- if (checkRightNode) {
166
- const rightNode = determineNode(expressionNode.right);
167
- if (rightNode.type === TSESTree.AST_NODE_TYPES.Identifier) checkAndReportIdentifier(rightNode, expressionNode.right);
129
+ /**
130
+ * Determines which diagnostic, if any, applies to a condition.
131
+ */
132
+ function getConditionError(node) {
133
+ const type = getConstrainedTypeAtLocation(services, node);
134
+ const typeParts = type.isUnion() ? type.types : [type];
135
+ let hasPotentiallyFalsyString = false;
136
+ let hasPotentiallyFalsyNumber = false;
137
+ for (const typePart of typeParts) {
138
+ if (typePart.isStringLiteral()) {
139
+ hasPotentiallyFalsyString ||= typePart.value === "";
140
+ continue;
141
+ }
142
+ if (typePart.isNumberLiteral()) {
143
+ hasPotentiallyFalsyNumber ||= typePart.value === 0;
144
+ continue;
145
+ }
146
+ const baseType = checker.getBaseTypeOfLiteralType(typePart);
147
+ if (baseType === stringType) {
148
+ hasPotentiallyFalsyString = true;
149
+ continue;
150
+ }
151
+ if (baseType === numberType) {
152
+ hasPotentiallyFalsyNumber = true;
153
+ continue;
154
+ }
155
+ if (baseType === bigintType) {
156
+ const value = "value" in typePart ? typePart.value : void 0;
157
+ hasPotentiallyFalsyNumber ||= !isNonZeroBigIntLiteralValue(value);
158
+ }
168
159
  }
160
+ if (!allowString && hasPotentiallyFalsyString) return "conditionErrorFalseyString";
161
+ if (!allowNumber && hasPotentiallyFalsyNumber) return "conditionErrorFalseyNumber";
169
162
  }
170
- function checkJSXExpression(node) {
171
- if (node.expression.type === TSESTree.AST_NODE_TYPES.LogicalExpression && node.expression.operator === "&&") checkLogicalExpression(node.expression, false);
163
+ /**
164
+ * Flattens:
165
+ *
166
+ * a && b && <Component />
167
+ *
168
+ * into:
169
+ *
170
+ * [a, b, <Component />]
171
+ *
172
+ * Every operand except the final one acts as a condition.
173
+ */
174
+ function collectAndOperands(node, operands = []) {
175
+ if (node.type === AST_NODE_TYPES.LogicalExpression && node.operator === "&&") {
176
+ collectAndOperands(node.left, operands);
177
+ collectAndOperands(node.right, operands);
178
+ } else operands.push(node);
179
+ return operands;
180
+ }
181
+ function reportCondition(node) {
182
+ const messageId = getConditionError(node);
183
+ if (!messageId) return;
184
+ context.report({
185
+ node,
186
+ messageId,
187
+ fix: (fixer) => {
188
+ if (node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.Literal || node.type === AST_NODE_TYPES.MemberExpression || node.type === AST_NODE_TYPES.CallExpression || node.type === AST_NODE_TYPES.ChainExpression) return fixer.insertTextBefore(node, "!!");
189
+ if (ASTUtils.isParenthesized(node, context.sourceCode)) {
190
+ const openingParen = context.sourceCode.getTokenBefore(node);
191
+ if (openingParen?.value === "(") return fixer.insertTextBefore(openingParen, "!!");
192
+ }
193
+ return [fixer.insertTextBefore(node, "!!("), fixer.insertTextAfter(node, ")")];
194
+ }
195
+ });
172
196
  }
173
- return { JSXExpressionContainer: checkJSXExpression };
197
+ return { JSXExpressionContainer(node) {
198
+ const expression = node.expression;
199
+ if (expression.type !== AST_NODE_TYPES.LogicalExpression || expression.operator !== "&&") return;
200
+ const operands = collectAndOperands(expression);
201
+ for (let index = 0; index < operands.length - 1; index += 1) reportCondition(operands[index]);
202
+ } };
174
203
  }
175
204
  });
176
205
 
177
- //#endregion
178
- //#region src/index.ts
206
+ // src/index.ts
179
207
  const ziloen = { rules: {
180
208
  "generic-spacing": genericSpacing,
181
209
  "jsx-strict-logical-expressions": jsxStrictLogicalExpressions
182
210
  } };
183
211
 
184
- //#endregion
185
212
  export { ziloen as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ziloen",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -14,20 +14,22 @@
14
14
  "test": "vitest"
15
15
  },
16
16
  "dependencies": {
17
- "@typescript-eslint/utils": "8.58.0"
17
+ "@typescript-eslint/type-utils": "^8.72.2",
18
+ "@typescript-eslint/utils": "8.72.2"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/eslint": "^9.6.1",
21
- "@types/node": "^24.1.0",
22
- "@typescript-eslint/parser": "8.58.0",
23
- "bumpp": "^11.0.1",
24
- "eslint": "^10.2.0",
25
- "eslint-vitest-rule-tester": "^3.1.0",
26
- "tsdown": "^0.21.7",
22
+ "@types/node": "^26.6.6",
23
+ "@typescript-eslint/parser": "8.72.2",
24
+ "bumpp": "^12.3.0",
25
+ "eslint": "^10.14.0",
26
+ "eslint-vitest-rule-tester": "^3.2.0",
27
+ "tsdown": "^0.23.7",
27
28
  "typescript": "6.0.2",
28
- "vitest": "^4.1.2"
29
+ "vitest": "^5.1.0"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "typescript": ">=6.0.0"
32
- }
33
+ },
34
+ "packageManager": "pnpm@12.18.0+sha512.9d7882d5dc82147ecfc4e062aa34834e0ad0296710a1837a1115ae012127d36b7109e321ac1598a1dd0a65597681b1e99917e12ed0d749eae91187943363beb3"
33
35
  }