oxlint-plugin-effect 0.12.0 → 0.13.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 CHANGED
@@ -27,14 +27,14 @@ If the project uses JSON configuration, copy the exported rule map into `rules`;
27
27
 
28
28
  | Rule | Contract |
29
29
  | -------------------------------------- | ----------------------------------------------------------------------------------------- |
30
- | `effect/noAs` | Bans TypeScript `as` assertions; use `satisfies` |
30
+ | `effect/noAs` | Bans TypeScript `as` assertions; use `satisfies` (`as const` is allowed) |
31
31
  | `effect/noAsyncFunction` | Bans async functions and await expressions |
32
32
  | `effect/noTryCatch` | Bans every try/catch/finally statement |
33
33
  | `effect/noTestLifecycleHooks` | Bans `beforeEach`, `afterEach`, `beforeAll`, and `afterAll`; use Effect scopes instead |
34
34
  | `effect/noThrowStatement` | Bans every throw statement |
35
35
  | `effect/noNewPromise` | Bans new Promise, Promise calls, and Promise static APIs |
36
36
  | `effect/noNewError` | Allows native Error values only as direct arguments to Effect.die, Cause.die, or Exit.die |
37
- | `effect/noNullish` | Bans null and undefined; use Option or a domain enum for richer state |
37
+ | `effect/noNullish` | Bans null and undefined; permits `Object.create(null)` for prototype-free dictionaries |
38
38
  | `effect/noModuleMocks` | Bans Vitest and Jest module mocks and method spies; use Effect service test layers |
39
39
  | `effect/noTernary` | Bans conditional expressions while allowing ordinary if statements |
40
40
  | `effect/noManagedRuntimeInEffect` | Keeps ManagedRuntime construction at non-Effect host boundaries |
@@ -1,21 +1,27 @@
1
+ import { narrow } from "../vendor/effect-oxlint/AST.js";
1
2
  import { make } from "../vendor/effect-oxlint/Diagnostic.js";
2
3
  import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
3
4
  import { define, meta } from "../vendor/effect-oxlint/Rule.js";
4
5
  import "../vendor/effect-oxlint/index.js";
6
+ import * as Option from "effect/Option";
7
+ import * as Effect from "effect/Effect";
5
8
  //#region src/rules/no-as.ts
6
- /** Ban TypeScript `as` assertions. */
9
+ const isConstAssertion = (node) => Option.exists(narrow(node, "TSAsExpression"), ({ typeAnnotation }) => typeAnnotation.type === "TSTypeReference" && typeAnnotation.typeName.type === "Identifier" && typeAnnotation.typeName.name === "const");
7
10
  const noAs = define({
8
11
  name: "no-as",
9
12
  meta: meta({
10
13
  type: "problem",
11
- description: "Use satisfies instead of an as assertion."
14
+ description: "Use satisfies instead of an as assertion; `as const` is allowed."
12
15
  }),
13
16
  create: function* () {
14
17
  const ctx = yield* RuleContext;
15
- return { TSAsExpression: (node) => ctx.report(make({
16
- node,
17
- message: "Avoid as assertions. Use satisfies instead."
18
- })) };
18
+ return { TSAsExpression: (node) => {
19
+ if (isConstAssertion(node)) return Effect.void;
20
+ return ctx.report(make({
21
+ node,
22
+ message: "Avoid as assertions. Use satisfies instead."
23
+ }));
24
+ } };
19
25
  }
20
26
  });
21
27
  //#endregion
@@ -1,4 +1,4 @@
1
- import { narrow } from "../vendor/effect-oxlint/AST.js";
1
+ import { isMember, narrow } from "../vendor/effect-oxlint/AST.js";
2
2
  import { make } from "../vendor/effect-oxlint/Diagnostic.js";
3
3
  import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
4
4
  import { define, meta } from "../vendor/effect-oxlint/Rule.js";
@@ -8,6 +8,10 @@ import * as Option from "effect/Option";
8
8
  import * as Effect from "effect/Effect";
9
9
  //#region src/rules/no-nullish.ts
10
10
  const message = "Avoid null and undefined. Use Option for presence or absence. Use a domain enum when the state has more than two cases.";
11
+ const isNullPrototypeArgument = (node) => {
12
+ const parent = node.parent;
13
+ return parent?.type === "CallExpression" && parent.arguments[0] === node && parent.callee.type === "MemberExpression" && isMember(parent.callee, "Object", "create");
14
+ };
11
15
  const noNullish = define({
12
16
  name: "no-nullish",
13
17
  meta: meta({
@@ -33,7 +37,7 @@ const noNullish = define({
33
37
  }),
34
38
  Literal: (node) => Option.match(narrow(node, "Literal"), {
35
39
  onNone: () => Effect.void,
36
- onSome: (literal) => literal.value === null && !("regex" in literal) ? report(literal) : Effect.void
40
+ onSome: (literal) => literal.value === null && !("regex" in literal) && !isNullPrototypeArgument(literal) ? report(literal) : Effect.void
37
41
  }),
38
42
  TSNullKeyword: (node) => Option.match(narrow(node, "TSNullKeyword"), {
39
43
  onNone: () => Effect.void,
@@ -211,4 +211,4 @@ const findAncestor = dual(2, (node, type) => {
211
211
  */
212
212
  const hasAncestor = dual(2, (node, type) => Option.isSome(findAncestor(node, type)));
213
213
  //#endregion
214
- export { AST_exports, calleeName, importSource, matchImport, matchMember, memberNames, narrow };
214
+ export { AST_exports, calleeName, importSource, isMember, matchImport, matchMember, memberNames, narrow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-effect",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "oxlint plugin with Effect-specific lint rules",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -61,8 +61,8 @@
61
61
  "@oxlint/plugins": "^1.64.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@changesets/changelog-github": "^0.7.0",
65
- "@changesets/cli": "^2.31.0",
64
+ "@changesets/changelog-github": "^1.0.1",
65
+ "@changesets/cli": "^3.0.3",
66
66
  "@effect/language-service": "latest",
67
67
  "@effect/vitest": "latest",
68
68
  "@types/bun": "latest",