eslint-plugin-function 0.2.4 → 0.3.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 (2) hide show
  1. package/dist/index.js +140 -18
  2. package/package.json +12 -12
package/dist/index.js CHANGED
@@ -4,13 +4,12 @@ import { P, isMatching, match } from "ts-pattern";
4
4
  import ts from "typescript";
5
5
  import * as AST from "@eslint-react/ast";
6
6
  import { toRegExp } from "@eslint-react/shared";
7
- import "@let/eff";
8
7
  import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
9
8
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
10
9
 
11
10
  //#region package.json
12
11
  var name = "eslint-plugin-function";
13
- var version = "0.2.3";
12
+ var version = "0.2.9";
14
13
 
15
14
  //#endregion
16
15
  //#region src/utils/create-rule.ts
@@ -78,17 +77,17 @@ function getTypeVariants(types) {
78
77
  } else if (booleans.length === 2) variants.add("boolean");
79
78
  const strings = types.filter(isStringType);
80
79
  if (strings.length > 0) {
81
- const evaluated = match(strings).when((types$1) => types$1.every(isTruthyStringType), () => "truthy string").when((types$1) => types$1.every(isFalsyStringType), () => "falsy string").otherwise(() => "string");
80
+ const evaluated = match(strings).when((types) => types.every(isTruthyStringType), () => "truthy string").when((types) => types.every(isFalsyStringType), () => "falsy string").otherwise(() => "string");
82
81
  variants.add(evaluated);
83
82
  }
84
83
  const bigints = types.filter(isBigIntType);
85
84
  if (bigints.length > 0) {
86
- const evaluated = match(bigints).when((types$1) => types$1.every(isTruthyBigIntType), () => "truthy bigint").when((types$1) => types$1.every(isFalsyBigIntType), () => "falsy bigint").otherwise(() => "bigint");
85
+ const evaluated = match(bigints).when((types) => types.every(isTruthyBigIntType), () => "truthy bigint").when((types) => types.every(isFalsyBigIntType), () => "falsy bigint").otherwise(() => "bigint");
87
86
  variants.add(evaluated);
88
87
  }
89
88
  const numbers = types.filter(isNumberType);
90
89
  if (numbers.length > 0) {
91
- const evaluated = match(numbers).when((types$1) => types$1.every(isTruthyNumberType), () => "truthy number").when((types$1) => types$1.every(isFalsyNumberType), () => "falsy number").otherwise(() => "number");
90
+ const evaluated = match(numbers).when((types) => types.every(isTruthyNumberType), () => "truthy number").when((types) => types.every(isFalsyNumberType), () => "falsy number").otherwise(() => "number");
92
91
  variants.add(evaluated);
93
92
  }
94
93
  if (types.some(isEnumType)) variants.add("enum");
@@ -142,6 +141,121 @@ function create$1(context) {
142
141
  return {};
143
142
  }
144
143
 
144
+ //#endregion
145
+ //#region ../../.pkgs/eff/dist/index.js
146
+ /**
147
+ * Creates a function that can be used in a data-last (aka `pipe`able) or
148
+ * data-first style.
149
+ *
150
+ * The first parameter to `dual` is either the arity of the uncurried function
151
+ * or a predicate that determines if the function is being used in a data-first
152
+ * or data-last style.
153
+ *
154
+ * Using the arity is the most common use case, but there are some cases where
155
+ * you may want to use a predicate. For example, if you have a function that
156
+ * takes an optional argument, you can use a predicate to determine if the
157
+ * function is being used in a data-first or data-last style.
158
+ *
159
+ * You can pass either the arity of the uncurried function or a predicate
160
+ * which determines if the function is being used in a data-first or
161
+ * data-last style.
162
+ *
163
+ * **Example** (Using arity to determine data-first or data-last style)
164
+ *
165
+ * ```ts
166
+ * import { dual, pipe } from "effect/Function"
167
+ *
168
+ * const sum = dual<
169
+ * (that: number) => (self: number) => number,
170
+ * (self: number, that: number) => number
171
+ * >(2, (self, that) => self + that)
172
+ *
173
+ * console.log(sum(2, 3)) // 5
174
+ * console.log(pipe(2, sum(3))) // 5
175
+ * ```
176
+ *
177
+ * **Example** (Using call signatures to define the overloads)
178
+ *
179
+ * ```ts
180
+ * import { dual, pipe } from "effect/Function"
181
+ *
182
+ * const sum: {
183
+ * (that: number): (self: number) => number
184
+ * (self: number, that: number): number
185
+ * } = dual(2, (self: number, that: number): number => self + that)
186
+ *
187
+ * console.log(sum(2, 3)) // 5
188
+ * console.log(pipe(2, sum(3))) // 5
189
+ * ```
190
+ *
191
+ * **Example** (Using a predicate to determine data-first or data-last style)
192
+ *
193
+ * ```ts
194
+ * import { dual, pipe } from "effect/Function"
195
+ *
196
+ * const sum = dual<
197
+ * (that: number) => (self: number) => number,
198
+ * (self: number, that: number) => number
199
+ * >(
200
+ * (args) => args.length === 2,
201
+ * (self, that) => self + that
202
+ * )
203
+ *
204
+ * console.log(sum(2, 3)) // 5
205
+ * console.log(pipe(2, sum(3))) // 5
206
+ * ```
207
+ *
208
+ * @param arity - The arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.
209
+ * @param body - The function to be curried.
210
+ * @since 1.0.0
211
+ */
212
+ const dual = function(arity, body) {
213
+ if (typeof arity === "function") return function() {
214
+ return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
215
+ };
216
+ switch (arity) {
217
+ case 0:
218
+ case 1: throw new RangeError(`Invalid arity ${arity}`);
219
+ case 2: return function(a, b) {
220
+ if (arguments.length >= 2) return body(a, b);
221
+ return function(self) {
222
+ return body(self, a);
223
+ };
224
+ };
225
+ case 3: return function(a, b, c) {
226
+ if (arguments.length >= 3) return body(a, b, c);
227
+ return function(self) {
228
+ return body(self, a, b);
229
+ };
230
+ };
231
+ default: return function() {
232
+ if (arguments.length >= arity) return body.apply(this, arguments);
233
+ const args = arguments;
234
+ return function(self) {
235
+ return body(self, ...args);
236
+ };
237
+ };
238
+ }
239
+ };
240
+ /**
241
+ * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
242
+ * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * import * as assert from "node:assert"
247
+ * import { compose } from "effect/Function"
248
+ *
249
+ * const increment = (n: number) => n + 1;
250
+ * const square = (n: number) => n * n;
251
+ *
252
+ * assert.strictEqual(compose(increment, square)(2), 9);
253
+ * ```
254
+ *
255
+ * @since 1.0.0
256
+ */
257
+ const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
258
+
145
259
  //#endregion
146
260
  //#region src/rules/function-return-boolean.ts
147
261
  const RULE_NAME = "function-return-boolean";
@@ -179,7 +293,7 @@ function create(context, [opts]) {
179
293
  const services = ESLintUtils.getParserServices(context, false);
180
294
  const pattern = toRegExp(opts?.pattern ?? defaultPattern);
181
295
  const functionEntries = [];
182
- function handleReturnExpression(context$1, returnExpression, onViolation) {
296
+ function handleReturnExpression(context, returnExpression, onViolation) {
183
297
  if (returnExpression == null) {
184
298
  onViolation(returnExpression, { variants: "nullish" });
185
299
  return;
@@ -190,20 +304,24 @@ function create(context, [opts]) {
190
304
  }
191
305
  return {
192
306
  [":function"](node) {
193
- const functionName = AST.getFunctionId(node)?.name;
194
- const isMatched = functionName != null && pattern.test(functionName);
307
+ const functionId = AST.getFunctionId(node);
195
308
  functionEntries.push({
196
- functionName,
197
- functionNode: node,
198
- isMatched
309
+ functionId,
310
+ functionNode: node
199
311
  });
200
312
  },
201
313
  [":function:exit"]() {
202
314
  functionEntries.pop();
203
315
  },
204
316
  ["ArrowFunctionExpression"](node) {
205
- const { functionName, isMatched = false } = functionEntries.at(-1) ?? {};
206
- if (functionName == null || !isMatched) return;
317
+ const { functionId, functionNode } = functionEntries.at(-1) ?? {};
318
+ if (functionId == null || functionNode == null) return;
319
+ const functionName = match(functionId).with({ type: AST_NODE_TYPES.Identifier }, (id) => id.name).with({
320
+ type: AST_NODE_TYPES.MemberExpression,
321
+ property: { type: AST_NODE_TYPES.Identifier }
322
+ }, (me) => me.property.name).otherwise(() => null);
323
+ if (functionName == null) return;
324
+ if (!pattern.test(functionName)) return;
207
325
  if (node.body.type === AST_NODE_TYPES.BlockStatement) return;
208
326
  handleReturnExpression(context, node.body, (expr, data) => {
209
327
  context.report({
@@ -217,17 +335,21 @@ function create(context, [opts]) {
217
335
  });
218
336
  },
219
337
  ["ReturnStatement"](node) {
220
- const { functionName, functionNode, isMatched = false } = functionEntries.at(-1) ?? {};
221
- if (functionName == null || functionNode == null || !isMatched) return;
338
+ const { functionId, functionNode } = functionEntries.at(-1) ?? {};
339
+ if (functionId == null || functionNode == null) return;
222
340
  handleReturnExpression(context, node.argument, (expr, data) => {
223
- const functionName$1 = AST.getFunctionId(functionNode)?.name;
224
- if (functionName$1 == null) return;
341
+ const functionName = match(functionId).with({ type: AST_NODE_TYPES.Identifier }, (id) => id.name).with({
342
+ type: AST_NODE_TYPES.MemberExpression,
343
+ property: { type: AST_NODE_TYPES.Identifier }
344
+ }, (me) => me.property.name).otherwise(() => null);
345
+ if (functionName == null) return;
346
+ if (!pattern.test(functionName)) return;
225
347
  context.report({
226
348
  messageId: "functionReturnBoolean",
227
349
  node: expr ?? node.argument ?? node,
228
350
  data: {
229
351
  ...data,
230
- functionName: functionName$1
352
+ functionName
231
353
  }
232
354
  });
233
355
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-function",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "(WIP) An ESLint plugin for function-related rules.",
6
6
  "homepage": "https://github.com/Rel1cx/dx",
@@ -27,24 +27,24 @@
27
27
  "package.json"
28
28
  ],
29
29
  "dependencies": {
30
- "@eslint-react/ast": "^2.5.3",
31
- "@eslint-react/shared": "^2.5.3",
32
- "@let/eff": "npm:@jsr/let__eff@^0.1.2",
33
- "@typescript-eslint/scope-manager": "^8.52.0",
34
- "@typescript-eslint/type-utils": "^8.52.0",
35
- "@typescript-eslint/types": "^8.52.0",
36
- "@typescript-eslint/utils": "^8.52.0",
30
+ "@eslint-react/ast": "^2.7.2",
31
+ "@eslint-react/shared": "^2.7.2",
32
+ "@typescript-eslint/scope-manager": "^8.53.1",
33
+ "@typescript-eslint/type-utils": "^8.53.1",
34
+ "@typescript-eslint/types": "^8.53.1",
35
+ "@typescript-eslint/utils": "^8.53.1",
37
36
  "string-ts": "^2.3.1",
38
37
  "ts-api-utils": "^2.4.0",
39
38
  "ts-pattern": "^5.9.0"
40
39
  },
41
40
  "devDependencies": {
42
- "@types/node": "^25.0.3",
43
- "@typescript-eslint/rule-tester": "^8.52.0",
41
+ "@types/node": "^25.0.10",
42
+ "@typescript-eslint/rule-tester": "^8.53.1",
44
43
  "dedent": "^1.7.1",
45
44
  "eslint": "^9.39.2",
46
- "tsdown": "^0.19.0-beta.5",
47
- "tsl": "^1.0.28"
45
+ "tsdown": "^0.20.1",
46
+ "tsl": "^1.0.28",
47
+ "@local/eff": "0.2.9"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "eslint": "^9.39.2",