@doubleloop-io/oxlint-plugin-effect 0.1.0-rc.1 → 0.1.0-rc.2
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 +74 -56
- package/RULES.md +520 -395
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -4
- package/dist/presets/all.json +2 -2
- package/dist/rules/adapter-import-boundary.js +5 -12
- package/dist/rules/all-errors-handled-at-boundary.js +3 -9
- package/dist/rules/anti-slop-no-service-constructor-imports.js +1 -8
- package/dist/rules/exported-schema-identifier.js +15 -10
- package/dist/rules/no-effect-promise.js +1 -8
- package/dist/rules/no-manual-discriminant-comparison.js +3 -9
- package/dist/rules/no-manual-discriminant-construction.js +3 -9
- package/dist/rules/no-provide-on-definition.js +1 -8
- package/dist/rules/no-throw-or-primitive-failure.d.ts +1 -0
- package/dist/rules/{no-bare-error-in-effect.js → no-throw-or-primitive-failure.js} +2 -9
- package/dist/rules/no-yielded-match.js +9 -2
- package/dist/rules/prefer-effect-vitest.js +1 -8
- package/dist/rules/provide-named-layer.js +1 -8
- package/dist/rules/redacted-secret-names.js +18 -5
- package/dist/rules/run-only-in-runner.js +1 -8
- package/dist/rules/service-key-matches-name.js +15 -6
- package/dist/rules/service-port-shape-only.js +10 -18
- package/dist/rules/single-style-per-function.js +1 -8
- package/dist/rules/split-success-path-from-error-handling.d.ts +1 -0
- package/dist/rules/{error-handling-in-effect-fn-handler.js → split-success-path-from-error-handling.js} +2 -9
- package/dist/shared/entry-files.d.ts +1 -1
- package/dist/shared/file-globs.d.ts +3 -9
- package/dist/shared/file-globs.js +3 -12
- package/dist/shared/settings.d.ts +5 -0
- package/dist/shared/settings.js +26 -0
- package/dist/shared/tagged-values.d.ts +2 -8
- package/dist/shared/tagged-values.js +3 -14
- package/dist/shared/test-files.d.ts +1 -1
- package/package.json +5 -4
- package/dist/rules/error-handling-in-effect-fn-handler.d.ts +0 -1
- package/dist/rules/no-bare-error-in-effect.d.ts +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { defineRule } from "@oxlint/plugins";
|
|
2
2
|
import { isEffectGenerator, isInsideEffectGeneratorBody, memberOf, nearestFunction, } from "../shared/effect-ast.js";
|
|
3
|
-
import { filePatternsSchema } from "../shared/file-globs.js";
|
|
4
3
|
import { isTestFile } from "../shared/test-files.js";
|
|
5
4
|
// mapError, orElse and orElseSucceed stay allowed: they name a failure or give an alternative
|
|
6
5
|
// without opening a handling branch inside the success path.
|
|
@@ -103,7 +102,7 @@ const isInsideIterationCallback = (node) => {
|
|
|
103
102
|
}
|
|
104
103
|
return false;
|
|
105
104
|
};
|
|
106
|
-
export const
|
|
105
|
+
export const splitSuccessPathFromErrorHandlingRule = defineRule({
|
|
107
106
|
meta: {
|
|
108
107
|
type: "suggestion",
|
|
109
108
|
docs: {
|
|
@@ -112,13 +111,7 @@ export const errorHandlingInEffectFnHandlerRule = defineRule({
|
|
|
112
111
|
messages: {
|
|
113
112
|
handlerInGenerator: "Move `Effect.{{name}}` out of the generator: keep the success path in the body and handle errors in the pipeable arguments of `Effect.fn`.",
|
|
114
113
|
},
|
|
115
|
-
schema: [
|
|
116
|
-
{
|
|
117
|
-
type: "object",
|
|
118
|
-
properties: { testFiles: filePatternsSchema },
|
|
119
|
-
additionalProperties: false,
|
|
120
|
-
},
|
|
121
|
-
],
|
|
114
|
+
schema: [],
|
|
122
115
|
},
|
|
123
116
|
createOnce(context) {
|
|
124
117
|
let inTestFile = false;
|
|
@@ -2,5 +2,5 @@ export declare const defaultEntryFiles: readonly ["**/main.ts", "**/cli.ts", "**
|
|
|
2
2
|
export declare const isEntryFile: (context: {
|
|
3
3
|
readonly filename: string;
|
|
4
4
|
readonly cwd: string;
|
|
5
|
-
readonly
|
|
5
|
+
readonly settings: Readonly<Record<string, unknown>>;
|
|
6
6
|
}) => boolean;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
type: string;
|
|
3
|
-
items: {
|
|
4
|
-
type: string;
|
|
5
|
-
};
|
|
6
|
-
uniqueItems: boolean;
|
|
7
|
-
};
|
|
1
|
+
import { type FileSettingKey } from "./settings.ts";
|
|
8
2
|
type FileContext = {
|
|
9
3
|
readonly filename: string;
|
|
10
4
|
readonly cwd: string;
|
|
11
|
-
readonly
|
|
5
|
+
readonly settings: Readonly<Record<string, unknown>>;
|
|
12
6
|
};
|
|
13
|
-
export declare const makeFileMatcher: (key:
|
|
7
|
+
export declare const makeFileMatcher: (key: FileSettingKey, defaults: ReadonlyArray<string>) => (context: FileContext) => boolean;
|
|
14
8
|
export {};
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import { matchesGlob, relative, sep } from "node:path";
|
|
2
|
-
|
|
3
|
-
const configuredPatterns = (options, key, defaults) => {
|
|
4
|
-
const [first] = options;
|
|
5
|
-
if (typeof first !== "object" || first === null)
|
|
6
|
-
return defaults;
|
|
7
|
-
const entry = Object.entries(first).find(([name]) => name === key);
|
|
8
|
-
if (entry === undefined)
|
|
9
|
-
return defaults;
|
|
10
|
-
const patterns = entry[1];
|
|
11
|
-
return Array.isArray(patterns) ? patterns.filter((pattern) => typeof pattern === "string") : [];
|
|
12
|
-
};
|
|
2
|
+
import { projectSetting } from "./settings.js";
|
|
13
3
|
// Globs match the path relative to the working directory, with `/` on every platform.
|
|
14
4
|
export const makeFileMatcher = (key, defaults) => (context) => {
|
|
15
5
|
const path = relative(context.cwd, context.filename).split(sep).join("/");
|
|
16
|
-
|
|
6
|
+
const patterns = projectSetting(context.settings, key) ?? defaults;
|
|
7
|
+
return patterns.some((pattern) => matchesGlob(path, pattern));
|
|
17
8
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
declare const settingKeys: readonly ["entryFiles", "testFiles", "discriminants"];
|
|
2
|
+
type SettingKey = (typeof settingKeys)[number];
|
|
3
|
+
export type FileSettingKey = Exclude<SettingKey, "discriminants">;
|
|
4
|
+
export declare const projectSetting: (settings: Readonly<Record<string, unknown>>, key: SettingKey) => readonly string[] | undefined;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const settingKeys = ["entryFiles", "testFiles", "discriminants"];
|
|
2
|
+
const isSettingKey = (name) => settingKeys.some((key) => key === name);
|
|
3
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
4
|
+
const invalid = (problem) => new Error(`Invalid settings["dl-effect"]: ${problem}. Known keys: ${settingKeys.join(", ")}.`);
|
|
5
|
+
// Every key is checked on every read, not only the requested one: a typo surfaces as soon as any
|
|
6
|
+
// enabled rule reads a setting, even when no rule reads the misspelled key.
|
|
7
|
+
export const projectSetting = (settings, key) => {
|
|
8
|
+
const pluginSettings = settings["dl-effect"];
|
|
9
|
+
if (pluginSettings === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (typeof pluginSettings !== "object" ||
|
|
12
|
+
pluginSettings === null ||
|
|
13
|
+
Array.isArray(pluginSettings)) {
|
|
14
|
+
throw invalid("expected an object");
|
|
15
|
+
}
|
|
16
|
+
let configured;
|
|
17
|
+
for (const [name, value] of Object.entries(pluginSettings)) {
|
|
18
|
+
if (!isSettingKey(name))
|
|
19
|
+
throw invalid(`unknown key "${name}"`);
|
|
20
|
+
if (!isStringArray(value))
|
|
21
|
+
throw invalid(`"${name}" must be an array of strings`);
|
|
22
|
+
if (name === key)
|
|
23
|
+
configured = value;
|
|
24
|
+
}
|
|
25
|
+
return configured;
|
|
26
|
+
};
|
|
@@ -6,12 +6,6 @@ export declare const isReasonTagMember: (node: ESTree.MemberExpression) => boole
|
|
|
6
6
|
export declare const tagMemberFromComparison: (node: ESTree.BinaryExpression) => ESTree.MemberExpression | undefined;
|
|
7
7
|
export declare const isInsideBroadEffectHandler: (node: ESTree.Node) => boolean;
|
|
8
8
|
export declare const propertyName: (property: ESTree.ObjectProperty) => string | undefined;
|
|
9
|
-
export declare const
|
|
10
|
-
|
|
11
|
-
readonly items: {
|
|
12
|
-
readonly type: "string";
|
|
13
|
-
};
|
|
14
|
-
readonly uniqueItems: true;
|
|
15
|
-
};
|
|
16
|
-
export declare const configuredDiscriminants: (options: ReadonlyArray<unknown>) => Set<string>;
|
|
9
|
+
export declare const defaultDiscriminants: readonly ["_tag"];
|
|
10
|
+
export declare const configuredDiscriminants: (settings: Readonly<Record<string, unknown>>) => Set<string>;
|
|
17
11
|
export declare const discriminantOf: (node: ESTree.Node | null | undefined, discriminants: ReadonlySet<string>) => string | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { calleeOf } from "./effect-ast.js";
|
|
2
|
+
import { projectSetting } from "./settings.js";
|
|
2
3
|
const equalityOperators = new Set(["==", "===", "!=", "!=="]);
|
|
3
4
|
const broadEffectCatchMethods = new Set(["catch", "catchAll", "catchIf"]);
|
|
4
5
|
export const isEqualityComparison = (node) => node.type === "BinaryExpression" && equalityOperators.has(node.operator);
|
|
@@ -39,20 +40,8 @@ export const propertyName = (property) => {
|
|
|
39
40
|
};
|
|
40
41
|
// The fields that discriminate a closed union: `_tag` for Effect's tagged values, plus whatever
|
|
41
42
|
// the codebase uses (`type`, `kind`). Shared by the rules on comparison and construction.
|
|
42
|
-
const defaultDiscriminants = ["_tag"];
|
|
43
|
-
export const
|
|
44
|
-
type: "array",
|
|
45
|
-
items: { type: "string" },
|
|
46
|
-
uniqueItems: true,
|
|
47
|
-
};
|
|
48
|
-
export const configuredDiscriminants = (options) => {
|
|
49
|
-
const [first] = options;
|
|
50
|
-
if (typeof first !== "object" || first === null || !("discriminants" in first)) {
|
|
51
|
-
return new Set(defaultDiscriminants);
|
|
52
|
-
}
|
|
53
|
-
const names = first.discriminants;
|
|
54
|
-
return new Set(Array.isArray(names) ? names.filter((name) => typeof name === "string") : []);
|
|
55
|
-
};
|
|
43
|
+
export const defaultDiscriminants = ["_tag"];
|
|
44
|
+
export const configuredDiscriminants = (settings) => new Set(projectSetting(settings, "discriminants") ?? defaultDiscriminants);
|
|
56
45
|
export const discriminantOf = (node, discriminants) => {
|
|
57
46
|
if (node?.type !== "MemberExpression")
|
|
58
47
|
return undefined;
|
|
@@ -2,5 +2,5 @@ export declare const defaultTestFiles: readonly ["**/*.test.*", "**/*.spec.*", "
|
|
|
2
2
|
export declare const isTestFile: (context: {
|
|
3
3
|
readonly filename: string;
|
|
4
4
|
readonly cwd: string;
|
|
5
|
-
readonly
|
|
5
|
+
readonly settings: Readonly<Record<string, unknown>>;
|
|
6
6
|
}) => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doubleloop-io/oxlint-plugin-effect",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.2",
|
|
4
4
|
"description": "Oxlint rules for Effect 4 code",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -50,14 +50,15 @@
|
|
|
50
50
|
"format:check": "oxfmt --check",
|
|
51
51
|
"typecheck": "tsc --noEmit",
|
|
52
52
|
"lint": "oxlint --deny-warnings",
|
|
53
|
-
"test": "node --test
|
|
53
|
+
"test": "node --test rules-md.test.ts rules/*.test.ts shared/*.test.ts scripts/*.test.ts",
|
|
54
54
|
"check": "pnpm format:check && pnpm typecheck && pnpm lint && pnpm test",
|
|
55
55
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node presets/all-json.ts",
|
|
56
56
|
"build:w": "node --watch-path=index.ts --watch-path=rules --watch-path=presets --watch-path=shared --watch-path=tsconfig.build.json scripts/build.ts",
|
|
57
57
|
"pack:check": "node --test presets/all-dist.test.ts && node scripts/pack-check.ts",
|
|
58
58
|
"build-and-pack": "pnpm check && pnpm build && pnpm pack:check && pnpm smoke-test",
|
|
59
59
|
"smoke-test": "node scripts/smoke-test.ts",
|
|
60
|
-
"
|
|
61
|
-
"consumer:
|
|
60
|
+
"release": "node scripts/release.ts",
|
|
61
|
+
"consumer:link": "./scripts/link-consumer.sh link",
|
|
62
|
+
"consumer:unlink": "./scripts/link-consumer.sh unlink"
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const errorHandlingInEffectFnHandlerRule: import("@oxlint/plugins").Rule;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const noBareErrorInEffectRule: import("@oxlint/plugins").Rule;
|