oxlint-plugin-effect 0.0.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/.effect-lsp.json +60 -0
- package/.oxlintrc.json +33 -0
- package/package.json +51 -0
- package/scripts/add-rule.ts +87 -0
- package/scripts/codegen.ts +108 -0
- package/src/index.ts +9 -0
- package/src/plugin.ts +16 -0
- package/src/presets/core.ts +36 -0
- package/src/presets/effect-native.ts +41 -0
- package/src/presets/full.ts +84 -0
- package/src/presets/index.ts +3 -0
- package/src/rules/_effect-context.ts +28 -0
- package/src/rules/index.ts +83 -0
- package/src/rules/no-arrow-ladder.ts +71 -0
- package/src/rules/no-async-function.ts +48 -0
- package/src/rules/no-console-in-effect.ts +46 -0
- package/src/rules/no-date-in-effect.ts +61 -0
- package/src/rules/no-dynamic-import.ts +13 -0
- package/src/rules/no-effect-as.ts +14 -0
- package/src/rules/no-effect-async.ts +14 -0
- package/src/rules/no-effect-bind.ts +13 -0
- package/src/rules/no-effect-do.ts +14 -0
- package/src/rules/no-effect-fn-generator.ts +59 -0
- package/src/rules/no-effect-gen-adapter.ts +51 -0
- package/src/rules/no-effect-map-void.ts +81 -0
- package/src/rules/no-effect-never.ts +14 -0
- package/src/rules/no-effect-orElse-ladder.ts +80 -0
- package/src/rules/no-effect-succeed-string.ts +53 -0
- package/src/rules/no-effect-succeed-variable.ts +51 -0
- package/src/rules/no-effect-succeed-void.ts +64 -0
- package/src/rules/no-effect-sync-console.ts +92 -0
- package/src/rules/no-effect-sync-wrapper.ts +68 -0
- package/src/rules/no-effect-type-alias.ts +72 -0
- package/src/rules/no-effect-wrapper-alias.ts +98 -0
- package/src/rules/no-extends-native-error.ts +55 -0
- package/src/rules/no-fetch-in-effect.ts +47 -0
- package/src/rules/no-flatmap-ladder.ts +79 -0
- package/src/rules/no-fromnullable-coalesce.ts +63 -0
- package/src/rules/no-global-console.ts +17 -0
- package/src/rules/no-global-date.ts +20 -0
- package/src/rules/no-global-fetch.ts +14 -0
- package/src/rules/no-global-random.ts +13 -0
- package/src/rules/no-global-timers.ts +17 -0
- package/src/rules/no-if-statement.ts +16 -0
- package/src/rules/no-iife-wrapper.ts +52 -0
- package/src/rules/no-inline-runtime-provide.ts +42 -0
- package/src/rules/no-instanceof-schema.ts +43 -0
- package/src/rules/no-json-in-effect.ts +45 -0
- package/src/rules/no-json-parse.ts +18 -0
- package/src/rules/no-manual-effect-channels.ts +59 -0
- package/src/rules/no-match-effect-branch.ts +83 -0
- package/src/rules/no-match-void-branch.ts +73 -0
- package/src/rules/no-nested-effect-call.ts +59 -0
- package/src/rules/no-nested-effect-gen.ts +44 -0
- package/src/rules/no-nested-pipe.ts +76 -0
- package/src/rules/no-new-error.ts +17 -0
- package/src/rules/no-new-promise.ts +14 -0
- package/src/rules/no-node-builtin-import.ts +70 -0
- package/src/rules/no-option-as.ts +13 -0
- package/src/rules/no-option-boolean-normalization.ts +89 -0
- package/src/rules/no-platform-globals.ts +117 -0
- package/src/rules/no-process-env-in-effect.ts +39 -0
- package/src/rules/no-process-env.ts +13 -0
- package/src/rules/no-random-in-effect.ts +39 -0
- package/src/rules/no-return-in-arrow.ts +77 -0
- package/src/rules/no-return-null.ts +47 -0
- package/src/rules/no-run-in-effect.ts +18 -0
- package/src/rules/no-runtime-run-fork.ts +14 -0
- package/src/rules/no-string-sentinel-const.ts +50 -0
- package/src/rules/no-switch-statement.ts +13 -0
- package/src/rules/no-ternary.ts +32 -0
- package/src/rules/no-throw-in-effect-gen.ts +42 -0
- package/src/rules/no-throw-statement.ts +13 -0
- package/src/rules/no-timers-in-effect.ts +47 -0
- package/src/rules/no-try-catch-in-effect-gen.ts +42 -0
- package/src/rules/no-try-catch.ts +14 -0
- package/src/rules/no-unnecessary-arrow-block.ts +52 -0
- package/src/rules/no-unnecessary-effect-gen.ts +85 -0
- package/src/rules/no-unnecessary-pipe.ts +46 -0
- package/src/vendor/effect-oxlint/AST.ts +516 -0
- package/src/vendor/effect-oxlint/Comment.ts +86 -0
- package/src/vendor/effect-oxlint/Diagnostic.ts +199 -0
- package/src/vendor/effect-oxlint/Plugin.ts +71 -0
- package/src/vendor/effect-oxlint/Rule.ts +590 -0
- package/src/vendor/effect-oxlint/RuleContext.ts +144 -0
- package/src/vendor/effect-oxlint/Scope.ts +177 -0
- package/src/vendor/effect-oxlint/SourceCode.ts +377 -0
- package/src/vendor/effect-oxlint/Testing.ts +1227 -0
- package/src/vendor/effect-oxlint/Token.ts +131 -0
- package/src/vendor/effect-oxlint/Visitor.ts +287 -0
- package/src/vendor/effect-oxlint/index.ts +88 -0
- package/tests/ban-rules.test.ts +252 -0
- package/tests/context-rules.test.ts +281 -0
- package/tests/pattern-rules.test.ts +258 -0
- package/tsconfig.json +15 -0
package/.effect-lsp.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"diagnostics": true,
|
|
3
|
+
"diagnosticsName": true,
|
|
4
|
+
"diagnosticSeverity": {
|
|
5
|
+
"anyUnknownInErrorContext": "error",
|
|
6
|
+
"deterministicKeys": "error",
|
|
7
|
+
"importFromBarrel": "error",
|
|
8
|
+
"instanceOfSchema": "error",
|
|
9
|
+
"missedPipeableOpportunity": "off",
|
|
10
|
+
"missingEffectServiceDependency": "error",
|
|
11
|
+
"schemaUnionOfLiterals": "error",
|
|
12
|
+
"strictBooleanExpressions": "off",
|
|
13
|
+
"strictEffectProvide": "error",
|
|
14
|
+
"catchAllToMapError": "error",
|
|
15
|
+
"catchUnfailableEffect": "error",
|
|
16
|
+
"effectFnOpportunity": "error",
|
|
17
|
+
"effectMapVoid": "error",
|
|
18
|
+
"effectSucceedWithVoid": "error",
|
|
19
|
+
"leakingRequirements": "error",
|
|
20
|
+
"preferSchemaOverJson": "error",
|
|
21
|
+
"redundantSchemaTagIdentifier": "error",
|
|
22
|
+
"returnEffectInGen": "error",
|
|
23
|
+
"runEffectInsideEffect": "error",
|
|
24
|
+
"schemaStructWithTag": "error",
|
|
25
|
+
"schemaSyncInEffect": "error",
|
|
26
|
+
"tryCatchInEffectGen": "error",
|
|
27
|
+
"unnecessaryEffectGen": "error",
|
|
28
|
+
"unnecessaryFailYieldableError": "error",
|
|
29
|
+
"unnecessaryPipe": "error",
|
|
30
|
+
"unnecessaryPipeChain": "error",
|
|
31
|
+
"extendsNativeError": "error",
|
|
32
|
+
"nodeBuiltinImport": "error",
|
|
33
|
+
"serviceNotAsClass": "error",
|
|
34
|
+
"outdatedApi": "error",
|
|
35
|
+
"globalFetch": "error",
|
|
36
|
+
"globalFetchInEffect": "error",
|
|
37
|
+
"globalDate": "error",
|
|
38
|
+
"globalDateInEffect": "error",
|
|
39
|
+
"globalConsole": "error",
|
|
40
|
+
"globalConsoleInEffect": "error",
|
|
41
|
+
"globalRandom": "error",
|
|
42
|
+
"globalRandomInEffect": "error",
|
|
43
|
+
"globalTimers": "error",
|
|
44
|
+
"globalTimersInEffect": "error",
|
|
45
|
+
"globalErrorInEffectCatch": "error",
|
|
46
|
+
"globalErrorInEffectFailure": "error"
|
|
47
|
+
},
|
|
48
|
+
"keyPatterns": [
|
|
49
|
+
{
|
|
50
|
+
"target": "service",
|
|
51
|
+
"pattern": "default",
|
|
52
|
+
"skipLeadingPath": ["src/"]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"target": "error",
|
|
56
|
+
"pattern": "default",
|
|
57
|
+
"skipLeadingPath": ["src/"]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
|
+
"categories": {
|
|
4
|
+
"correctness": "error",
|
|
5
|
+
"suspicious": "error",
|
|
6
|
+
"perf": "error"
|
|
7
|
+
},
|
|
8
|
+
"plugins": ["typescript", "import"],
|
|
9
|
+
"rules": {
|
|
10
|
+
"typescript/no-explicit-any": "error",
|
|
11
|
+
"typescript/no-non-null-assertion": "error",
|
|
12
|
+
"typescript/no-extra-non-null-assertion": "error",
|
|
13
|
+
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
14
|
+
"typescript/consistent-type-imports": [
|
|
15
|
+
"error",
|
|
16
|
+
{ "prefer": "type-imports", "fixStyle": "separate-type-imports" }
|
|
17
|
+
],
|
|
18
|
+
"typescript/no-unsafe-type-assertion": "error",
|
|
19
|
+
"import/no-duplicates": "error",
|
|
20
|
+
"no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }]
|
|
21
|
+
},
|
|
22
|
+
"ignorePatterns": ["**/dist/**", "**/node_modules/**", "**/*.d.ts"],
|
|
23
|
+
"overrides": [
|
|
24
|
+
{
|
|
25
|
+
"files": ["**/*.test.ts", "**/tests/**"],
|
|
26
|
+
"rules": {
|
|
27
|
+
"typescript/no-non-null-assertion": "off",
|
|
28
|
+
"typescript/no-explicit-any": "off",
|
|
29
|
+
"typescript/no-unsafe-type-assertion": "off"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oxlint-plugin-effect",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "oxlint plugin with Effect-specific lint rules",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": { "type": "git", "url": "https://github.com/cevr/effect-oxlint" },
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./plugin": "./src/plugin.ts",
|
|
11
|
+
"./presets/*": "./src/presets/*.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run src/index.ts",
|
|
15
|
+
"build": "bun run scripts/build.ts",
|
|
16
|
+
"typecheck": "tsgo --noEmit",
|
|
17
|
+
"lint": "concurrently -n ox,effect -c yellow,blue \"oxlint\" \"bun run lint:effect\"",
|
|
18
|
+
"lint:ox": "oxlint",
|
|
19
|
+
"lint:effect": "effect-language-service diagnostics --project tsconfig.json --lspconfig \"$(cat .effect-lsp.json)\"",
|
|
20
|
+
"lint:fix": "oxlint --fix",
|
|
21
|
+
"fmt": "oxfmt",
|
|
22
|
+
"fmt:check": "oxfmt --check",
|
|
23
|
+
"test": "bun test",
|
|
24
|
+
"gate": "concurrently -n type,style,test -c blue,yellow,green \"bun run typecheck\" \"bun run lint\" \"bun run test\"",
|
|
25
|
+
"codegen": "bun run scripts/codegen.ts",
|
|
26
|
+
"add-rule": "bun run scripts/add-rule.ts",
|
|
27
|
+
"version": "changeset version",
|
|
28
|
+
"release": "changeset publish"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@oxlint/plugins": "^1.60.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"effect": ">=4.0.0-beta.49"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@changesets/changelog-github": "^0.6.0",
|
|
38
|
+
"@changesets/cli": "^2.30.0",
|
|
39
|
+
"@effect/language-service": "latest",
|
|
40
|
+
"@effect/vitest": "latest",
|
|
41
|
+
"@types/bun": "latest",
|
|
42
|
+
"@typescript/native-preview": "latest",
|
|
43
|
+
"concurrently": "latest",
|
|
44
|
+
"effect": "4.0.0-beta.49",
|
|
45
|
+
"lefthook": "latest",
|
|
46
|
+
"oxfmt": "latest",
|
|
47
|
+
"oxlint": "latest",
|
|
48
|
+
"typescript": "latest",
|
|
49
|
+
"vitest": "latest"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold a new rule file with boilerplate.
|
|
3
|
+
*
|
|
4
|
+
* Usage: bun run scripts/add-rule.ts <rule-name> [--context]
|
|
5
|
+
*
|
|
6
|
+
* Examples:
|
|
7
|
+
* bun run scripts/add-rule.ts no-effect-zip
|
|
8
|
+
* bun run scripts/add-rule.ts no-await-in-effect --context
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, writeFileSync } from "node:fs"
|
|
11
|
+
import { join } from "node:path"
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2)
|
|
14
|
+
const contextFlag = args.includes("--context")
|
|
15
|
+
const ruleName = args.find((a) => !a.startsWith("--"))
|
|
16
|
+
|
|
17
|
+
if (!ruleName) {
|
|
18
|
+
console.error("Usage: bun run scripts/add-rule.ts <rule-name> [--context]")
|
|
19
|
+
console.error(" --context Generate an effect-context-aware rule (uses makeEffectContextTracker)")
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Convert kebab-case to camelCase
|
|
24
|
+
const camelCase = ruleName.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())
|
|
25
|
+
|
|
26
|
+
const filePath = join(import.meta.dir, `../src/rules/${ruleName}.ts`)
|
|
27
|
+
|
|
28
|
+
if (existsSync(filePath)) {
|
|
29
|
+
console.error(`✗ File already exists: ${filePath}`)
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const basicTemplate = `/**
|
|
34
|
+
* TODO: Description for ${ruleName}
|
|
35
|
+
*/
|
|
36
|
+
import type { ESTree } from "@oxlint/plugins"
|
|
37
|
+
import { AST, Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
|
|
38
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
39
|
+
import * as Effect from "effect/Effect"
|
|
40
|
+
|
|
41
|
+
export const ${camelCase} = Rule.define({
|
|
42
|
+
name: "${ruleName}",
|
|
43
|
+
meta: Rule.meta({
|
|
44
|
+
type: "suggestion",
|
|
45
|
+
description: "TODO: describe what this rule checks.",
|
|
46
|
+
}),
|
|
47
|
+
create: function* () {
|
|
48
|
+
const ctx = yield* RuleContext
|
|
49
|
+
return {
|
|
50
|
+
// TODO: add visitor handlers
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
`
|
|
55
|
+
|
|
56
|
+
const contextTemplate = `/**
|
|
57
|
+
* TODO: Description for ${ruleName}
|
|
58
|
+
*/
|
|
59
|
+
import type { ESTree } from "@oxlint/plugins"
|
|
60
|
+
import { AST, Diagnostic, Rule, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
61
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
62
|
+
import * as Effect from "effect/Effect"
|
|
63
|
+
import * as Ref from "effect/Ref"
|
|
64
|
+
|
|
65
|
+
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
66
|
+
|
|
67
|
+
export const ${camelCase} = Rule.define({
|
|
68
|
+
name: "${ruleName}",
|
|
69
|
+
meta: Rule.meta({
|
|
70
|
+
type: "suggestion",
|
|
71
|
+
description: "TODO: describe what this rule checks.",
|
|
72
|
+
}),
|
|
73
|
+
create: function* () {
|
|
74
|
+
const ctx = yield* RuleContext
|
|
75
|
+
const [depth, tracker] = yield* makeEffectContextTracker
|
|
76
|
+
|
|
77
|
+
return Visitor.merge(tracker, {
|
|
78
|
+
// TODO: add visitor handlers that check Ref.get(depth) > 0
|
|
79
|
+
})
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
`
|
|
83
|
+
|
|
84
|
+
writeFileSync(filePath, contextFlag ? contextTemplate : basicTemplate)
|
|
85
|
+
console.log(`✓ Created ${filePath}`)
|
|
86
|
+
console.log(` Export name: ${camelCase}`)
|
|
87
|
+
console.log(` Run \`bun run codegen\` to update src/rules/index.ts`)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generate src/rules/index.ts barrel from rule files.
|
|
3
|
+
*
|
|
4
|
+
* Scans src/rules/ for rule files (ignoring _*.ts and index.ts),
|
|
5
|
+
* extracts the exported const name, and writes the barrel.
|
|
6
|
+
*
|
|
7
|
+
* Usage: bun run scripts/codegen.ts
|
|
8
|
+
*/
|
|
9
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
10
|
+
import { join } from "node:path"
|
|
11
|
+
|
|
12
|
+
const RULES_DIR = join(import.meta.dir, "../src/rules")
|
|
13
|
+
const INDEX_PATH = join(RULES_DIR, "index.ts")
|
|
14
|
+
|
|
15
|
+
interface RuleEntry {
|
|
16
|
+
fileName: string
|
|
17
|
+
exportName: string
|
|
18
|
+
category: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const categoryOrder = [
|
|
22
|
+
"API bans",
|
|
23
|
+
"Global bans",
|
|
24
|
+
"Import bans",
|
|
25
|
+
"Statement bans",
|
|
26
|
+
"JSON",
|
|
27
|
+
"AST pattern rules",
|
|
28
|
+
"Effect-context rules",
|
|
29
|
+
"Other",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
function detectCategory(content: string, fileName: string): string {
|
|
33
|
+
if (content.includes("makeEffectContextTracker")) return "Effect-context rules"
|
|
34
|
+
if (content.includes("Rule.banImport") || content.includes("banImport")) return "Import bans"
|
|
35
|
+
if (content.includes("Rule.banStatement")) return "Statement bans"
|
|
36
|
+
if (content.includes("Rule.banMember") || content.includes("Rule.banMultiple")) {
|
|
37
|
+
if (fileName.includes("global") || fileName.includes("new-")) return "Global bans"
|
|
38
|
+
if (fileName.includes("json")) return "JSON"
|
|
39
|
+
if (fileName.includes("process")) return "Global bans"
|
|
40
|
+
return "API bans"
|
|
41
|
+
}
|
|
42
|
+
if (content.includes("Rule.banCallOf") || content.includes("banCallOf")) {
|
|
43
|
+
if (fileName.includes("global") || fileName.includes("timer")) return "Global bans"
|
|
44
|
+
return "API bans"
|
|
45
|
+
}
|
|
46
|
+
return "AST pattern rules"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractExportName(content: string): string | null {
|
|
50
|
+
const match = content.match(/export const (\w+)/)
|
|
51
|
+
return match?.[1] ?? null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const files = readdirSync(RULES_DIR)
|
|
55
|
+
.filter((f) => f.endsWith(".ts") && !f.startsWith("_") && f !== "index.ts")
|
|
56
|
+
.sort()
|
|
57
|
+
|
|
58
|
+
const entries: RuleEntry[] = []
|
|
59
|
+
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
const content = readFileSync(join(RULES_DIR, file), "utf-8")
|
|
62
|
+
const exportName = extractExportName(content)
|
|
63
|
+
if (!exportName) {
|
|
64
|
+
console.warn(`⚠ No export found in ${file}, skipping`)
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
entries.push({
|
|
68
|
+
fileName: file.replace(".ts", ""),
|
|
69
|
+
exportName,
|
|
70
|
+
category: detectCategory(content, file),
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Group by category
|
|
75
|
+
const grouped = new Map<string, RuleEntry[]>()
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const list = grouped.get(entry.category) ?? []
|
|
78
|
+
list.push(entry)
|
|
79
|
+
grouped.set(entry.category, list)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build output
|
|
83
|
+
const lines: string[] = [
|
|
84
|
+
'/**',
|
|
85
|
+
' * All Effect oxlint rules, exported by rule name for Plugin.define.',
|
|
86
|
+
' *',
|
|
87
|
+
' * AUTO-GENERATED by scripts/codegen.ts — do not edit manually.',
|
|
88
|
+
' */',
|
|
89
|
+
'',
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for (const cat of categoryOrder) {
|
|
93
|
+
const catEntries = grouped.get(cat)
|
|
94
|
+
if (!catEntries || catEntries.length === 0) continue
|
|
95
|
+
lines.push(`// --- ${cat} ---`)
|
|
96
|
+
for (const entry of catEntries) {
|
|
97
|
+
lines.push(`export { ${entry.exportName} } from "./${entry.fileName}.js"`)
|
|
98
|
+
}
|
|
99
|
+
lines.push('')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const output = lines.join('\n')
|
|
103
|
+
writeFileSync(INDEX_PATH, output)
|
|
104
|
+
|
|
105
|
+
console.log(`✓ Generated ${INDEX_PATH} with ${entries.length} rules`)
|
|
106
|
+
for (const [cat, catEntries] of grouped) {
|
|
107
|
+
console.log(` ${cat}: ${catEntries.length}`)
|
|
108
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* effect-oxlint-plugin
|
|
3
|
+
*
|
|
4
|
+
* oxlint plugin providing Effect-specific lint rules.
|
|
5
|
+
* Ports rules from biome-effect-linting-rules and effect-ts/language-service.
|
|
6
|
+
*/
|
|
7
|
+
export { default as plugin } from "./plugin.js"
|
|
8
|
+
export * as rules from "./rules/index.js"
|
|
9
|
+
export * as presets from "./presets/index.js"
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Effect oxlint plugin — all rules registered under the "effect" prefix.
|
|
3
|
+
*
|
|
4
|
+
* Load this in your `.oxlintrc.json`:
|
|
5
|
+
* ```json
|
|
6
|
+
* { "jsPlugins": ["effect-oxlint-plugin/plugin"] }
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
import { Plugin } from "./vendor/effect-oxlint/index.js"
|
|
10
|
+
|
|
11
|
+
import * as rules from "./rules/index.js"
|
|
12
|
+
|
|
13
|
+
export default Plugin.define({
|
|
14
|
+
name: "effect",
|
|
15
|
+
rules,
|
|
16
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core preset — essential Effect rules without global bans.
|
|
3
|
+
* Good starting point for projects transitioning to Effect.
|
|
4
|
+
*/
|
|
5
|
+
export const core = {
|
|
6
|
+
// API bans
|
|
7
|
+
"effect/noEffectDo": "error",
|
|
8
|
+
"effect/noEffectNever": "error",
|
|
9
|
+
"effect/noEffectAs": "error",
|
|
10
|
+
"effect/noEffectBind": "error",
|
|
11
|
+
"effect/noRunInEffect": "error",
|
|
12
|
+
|
|
13
|
+
// AST pattern rules
|
|
14
|
+
"effect/noNestedPipe": "error",
|
|
15
|
+
"effect/noNestedEffectGen": "error",
|
|
16
|
+
"effect/noUnnecessaryEffectGen": "warn",
|
|
17
|
+
"effect/noEffectSucceedVoid": "warn",
|
|
18
|
+
"effect/noEffectMapVoid": "warn",
|
|
19
|
+
"effect/noUnnecessaryPipe": "warn",
|
|
20
|
+
"effect/noEffectFnGenerator": "warn",
|
|
21
|
+
"effect/noFlatmapLadder": "error",
|
|
22
|
+
"effect/noNestedEffectCall": "error",
|
|
23
|
+
"effect/noIifeWrapper": "warn",
|
|
24
|
+
"effect/noFromNullableCoalesce": "warn",
|
|
25
|
+
"effect/noUnnecessaryArrowBlock": "warn",
|
|
26
|
+
"effect/noEffectGenAdapter": "warn",
|
|
27
|
+
"effect/noEffectSyncConsole": "error",
|
|
28
|
+
"effect/noArrowLadder": "warn",
|
|
29
|
+
|
|
30
|
+
// Effect-context rules
|
|
31
|
+
"effect/noThrowInEffectGen": "error",
|
|
32
|
+
"effect/noTryCatchInEffectGen": "warn",
|
|
33
|
+
"effect/noConsoleInEffect": "warn",
|
|
34
|
+
"effect/noInlineRuntimeProvide": "warn",
|
|
35
|
+
"effect/noPlatformGlobals": "warn",
|
|
36
|
+
} as const
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect-native preset — for codebases fully committed to Effect.
|
|
3
|
+
* Bans all globals and builtins in favor of Effect platform services.
|
|
4
|
+
*/
|
|
5
|
+
export const effectNative = {
|
|
6
|
+
// Global bans (everywhere)
|
|
7
|
+
"effect/noGlobalFetch": "error",
|
|
8
|
+
"effect/noGlobalConsole": "error",
|
|
9
|
+
"effect/noGlobalDate": "error",
|
|
10
|
+
"effect/noGlobalRandom": "error",
|
|
11
|
+
"effect/noGlobalTimers": "error",
|
|
12
|
+
"effect/noNewPromise": "error",
|
|
13
|
+
"effect/noNewError": "error",
|
|
14
|
+
"effect/noProcessEnv": "error",
|
|
15
|
+
"effect/noNodeBuiltinImport": "error",
|
|
16
|
+
"effect/noThrowStatement": "error",
|
|
17
|
+
"effect/noTryCatch": "error",
|
|
18
|
+
"effect/noReturnNull": "error",
|
|
19
|
+
"effect/noJsonParse": "error",
|
|
20
|
+
"effect/noExtendsNativeError": "error",
|
|
21
|
+
"effect/noAsyncFunction": "error",
|
|
22
|
+
"effect/noInstanceofSchema": "error",
|
|
23
|
+
"effect/noRuntimeRunFork": "error",
|
|
24
|
+
"effect/noDynamicImport": "error",
|
|
25
|
+
"effect/noSwitchStatement": "error",
|
|
26
|
+
|
|
27
|
+
// Effect-context (inside Effect.gen/fn)
|
|
28
|
+
"effect/noFetchInEffect": "error",
|
|
29
|
+
"effect/noDateInEffect": "error",
|
|
30
|
+
"effect/noRandomInEffect": "error",
|
|
31
|
+
"effect/noTimersInEffect": "error",
|
|
32
|
+
"effect/noJsonInEffect": "error",
|
|
33
|
+
"effect/noProcessEnvInEffect": "error",
|
|
34
|
+
"effect/noPlatformGlobals": "error",
|
|
35
|
+
|
|
36
|
+
// Type hygiene
|
|
37
|
+
"effect/noEffectTypeAlias": "error",
|
|
38
|
+
"effect/noManualEffectChannels": "error",
|
|
39
|
+
"effect/noEffectSyncConsole": "error",
|
|
40
|
+
"effect/noEffectSyncWrapper": "warn",
|
|
41
|
+
} as const
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full preset — all rules enabled.
|
|
3
|
+
*/
|
|
4
|
+
export const full = {
|
|
5
|
+
// API bans
|
|
6
|
+
"effect/noEffectDo": "error",
|
|
7
|
+
"effect/noEffectNever": "error",
|
|
8
|
+
"effect/noEffectAs": "error",
|
|
9
|
+
"effect/noEffectAsync": "error",
|
|
10
|
+
"effect/noEffectBind": "error",
|
|
11
|
+
"effect/noOptionAs": "error",
|
|
12
|
+
"effect/noRuntimeRunFork": "error",
|
|
13
|
+
"effect/noRunInEffect": "error",
|
|
14
|
+
|
|
15
|
+
// Global bans
|
|
16
|
+
"effect/noGlobalFetch": "warn",
|
|
17
|
+
"effect/noGlobalConsole": "warn",
|
|
18
|
+
"effect/noGlobalDate": "warn",
|
|
19
|
+
"effect/noGlobalRandom": "warn",
|
|
20
|
+
"effect/noGlobalTimers": "warn",
|
|
21
|
+
"effect/noNewPromise": "warn",
|
|
22
|
+
"effect/noNewError": "warn",
|
|
23
|
+
"effect/noProcessEnv": "warn",
|
|
24
|
+
|
|
25
|
+
// Import bans
|
|
26
|
+
"effect/noNodeBuiltinImport": "warn",
|
|
27
|
+
"effect/noDynamicImport": "error",
|
|
28
|
+
|
|
29
|
+
// Statement bans
|
|
30
|
+
"effect/noThrowStatement": "error",
|
|
31
|
+
"effect/noTryCatch": "warn",
|
|
32
|
+
"effect/noReturnNull": "warn",
|
|
33
|
+
"effect/noSwitchStatement": "warn",
|
|
34
|
+
"effect/noIfStatement": "off",
|
|
35
|
+
"effect/noTernary": "off",
|
|
36
|
+
|
|
37
|
+
// JSON
|
|
38
|
+
"effect/noJsonParse": "warn",
|
|
39
|
+
|
|
40
|
+
// AST pattern rules
|
|
41
|
+
"effect/noNestedPipe": "error",
|
|
42
|
+
"effect/noNestedEffectGen": "error",
|
|
43
|
+
"effect/noUnnecessaryEffectGen": "warn",
|
|
44
|
+
"effect/noEffectSucceedVoid": "warn",
|
|
45
|
+
"effect/noEffectMapVoid": "warn",
|
|
46
|
+
"effect/noUnnecessaryPipe": "warn",
|
|
47
|
+
"effect/noExtendsNativeError": "warn",
|
|
48
|
+
"effect/noEffectFnGenerator": "warn",
|
|
49
|
+
"effect/noInstanceofSchema": "warn",
|
|
50
|
+
"effect/noAsyncFunction": "warn",
|
|
51
|
+
"effect/noFlatmapLadder": "error",
|
|
52
|
+
"effect/noNestedEffectCall": "error",
|
|
53
|
+
"effect/noIifeWrapper": "warn",
|
|
54
|
+
"effect/noEffectSucceedString": "warn",
|
|
55
|
+
"effect/noEffectOrElseLadder": "warn",
|
|
56
|
+
"effect/noFromNullableCoalesce": "warn",
|
|
57
|
+
"effect/noUnnecessaryArrowBlock": "warn",
|
|
58
|
+
"effect/noEffectGenAdapter": "warn",
|
|
59
|
+
"effect/noEffectSyncConsole": "error",
|
|
60
|
+
"effect/noEffectTypeAlias": "warn",
|
|
61
|
+
"effect/noEffectWrapperAlias": "warn",
|
|
62
|
+
"effect/noEffectSucceedVariable": "warn",
|
|
63
|
+
"effect/noManualEffectChannels": "warn",
|
|
64
|
+
"effect/noMatchVoidBranch": "warn",
|
|
65
|
+
"effect/noOptionBooleanNormalization": "warn",
|
|
66
|
+
"effect/noReturnInArrow": "off",
|
|
67
|
+
"effect/noStringSentinelConst": "off",
|
|
68
|
+
"effect/noEffectSyncWrapper": "off",
|
|
69
|
+
"effect/noArrowLadder": "warn",
|
|
70
|
+
"effect/noMatchEffectBranch": "warn",
|
|
71
|
+
|
|
72
|
+
// Effect-context rules
|
|
73
|
+
"effect/noThrowInEffectGen": "error",
|
|
74
|
+
"effect/noTryCatchInEffectGen": "warn",
|
|
75
|
+
"effect/noConsoleInEffect": "warn",
|
|
76
|
+
"effect/noFetchInEffect": "warn",
|
|
77
|
+
"effect/noDateInEffect": "warn",
|
|
78
|
+
"effect/noRandomInEffect": "warn",
|
|
79
|
+
"effect/noTimersInEffect": "warn",
|
|
80
|
+
"effect/noJsonInEffect": "warn",
|
|
81
|
+
"effect/noProcessEnvInEffect": "warn",
|
|
82
|
+
"effect/noPlatformGlobals": "warn",
|
|
83
|
+
"effect/noInlineRuntimeProvide": "warn",
|
|
84
|
+
} as const
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for rules that need to detect "inside Effect context"
|
|
3
|
+
* (i.e., inside an Effect.gen or Effect.fn generator body).
|
|
4
|
+
*
|
|
5
|
+
* Uses Visitor.tracked to maintain a depth counter.
|
|
6
|
+
*/
|
|
7
|
+
import type { ESTree } from "@oxlint/plugins"
|
|
8
|
+
import { AST, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
import * as Effect from "effect/Effect"
|
|
10
|
+
import * as Ref from "effect/Ref"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a CallExpression is Effect.gen or Effect.fn.
|
|
14
|
+
* Both patterns establish an "Effect context" where globals should be avoided.
|
|
15
|
+
*/
|
|
16
|
+
export const isEffectContext = (node: ESTree.CallExpression): boolean =>
|
|
17
|
+
AST.isCallOf(node, "Effect", "gen") || AST.isCallOf(node, "Effect", "fn")
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a tracked visitor pair for Effect context depth.
|
|
21
|
+
* Returns [depthRef, trackerVisitor] so rules can merge the tracker
|
|
22
|
+
* and read the depth in their own handlers.
|
|
23
|
+
*/
|
|
24
|
+
export const makeEffectContextTracker = Effect.gen(function* () {
|
|
25
|
+
const depth = yield* Ref.make(0)
|
|
26
|
+
const tracker = Visitor.tracked("CallExpression", isEffectContext, depth)
|
|
27
|
+
return [depth, tracker] as const
|
|
28
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All Effect oxlint rules, exported by rule name for Plugin.define.
|
|
3
|
+
*
|
|
4
|
+
* AUTO-GENERATED by scripts/codegen.ts — do not edit manually.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// --- API bans ---
|
|
8
|
+
export { noEffectAs } from "./no-effect-as.js"
|
|
9
|
+
export { noEffectAsync } from "./no-effect-async.js"
|
|
10
|
+
export { noEffectBind } from "./no-effect-bind.js"
|
|
11
|
+
export { noEffectDo } from "./no-effect-do.js"
|
|
12
|
+
export { noEffectNever } from "./no-effect-never.js"
|
|
13
|
+
export { noOptionAs } from "./no-option-as.js"
|
|
14
|
+
export { noRunInEffect } from "./no-run-in-effect.js"
|
|
15
|
+
export { noRuntimeRunFork } from "./no-runtime-run-fork.js"
|
|
16
|
+
|
|
17
|
+
// --- Global bans ---
|
|
18
|
+
export { noGlobalConsole } from "./no-global-console.js"
|
|
19
|
+
export { noGlobalDate } from "./no-global-date.js"
|
|
20
|
+
export { noGlobalFetch } from "./no-global-fetch.js"
|
|
21
|
+
export { noGlobalRandom } from "./no-global-random.js"
|
|
22
|
+
export { noGlobalTimers } from "./no-global-timers.js"
|
|
23
|
+
export { noProcessEnv } from "./no-process-env.js"
|
|
24
|
+
|
|
25
|
+
// --- Statement bans ---
|
|
26
|
+
export { noDynamicImport } from "./no-dynamic-import.js"
|
|
27
|
+
export { noIfStatement } from "./no-if-statement.js"
|
|
28
|
+
export { noSwitchStatement } from "./no-switch-statement.js"
|
|
29
|
+
export { noThrowStatement } from "./no-throw-statement.js"
|
|
30
|
+
export { noTryCatch } from "./no-try-catch.js"
|
|
31
|
+
|
|
32
|
+
// --- JSON ---
|
|
33
|
+
export { noJsonParse } from "./no-json-parse.js"
|
|
34
|
+
|
|
35
|
+
// --- AST pattern rules ---
|
|
36
|
+
export { noArrowLadder } from "./no-arrow-ladder.js"
|
|
37
|
+
export { noAsyncFunction } from "./no-async-function.js"
|
|
38
|
+
export { noEffectFnGenerator } from "./no-effect-fn-generator.js"
|
|
39
|
+
export { noEffectGenAdapter } from "./no-effect-gen-adapter.js"
|
|
40
|
+
export { noEffectMapVoid } from "./no-effect-map-void.js"
|
|
41
|
+
export { noEffectOrElseLadder } from "./no-effect-orElse-ladder.js"
|
|
42
|
+
export { noEffectSucceedString } from "./no-effect-succeed-string.js"
|
|
43
|
+
export { noEffectSucceedVariable } from "./no-effect-succeed-variable.js"
|
|
44
|
+
export { noEffectSucceedVoid } from "./no-effect-succeed-void.js"
|
|
45
|
+
export { noEffectSyncConsole } from "./no-effect-sync-console.js"
|
|
46
|
+
export { noEffectSyncWrapper } from "./no-effect-sync-wrapper.js"
|
|
47
|
+
export { noEffectTypeAlias } from "./no-effect-type-alias.js"
|
|
48
|
+
export { noEffectWrapperAlias } from "./no-effect-wrapper-alias.js"
|
|
49
|
+
export { noExtendsNativeError } from "./no-extends-native-error.js"
|
|
50
|
+
export { noFlatmapLadder } from "./no-flatmap-ladder.js"
|
|
51
|
+
export { noFromNullableCoalesce } from "./no-fromnullable-coalesce.js"
|
|
52
|
+
export { noIifeWrapper } from "./no-iife-wrapper.js"
|
|
53
|
+
export { noInstanceofSchema } from "./no-instanceof-schema.js"
|
|
54
|
+
export { noManualEffectChannels } from "./no-manual-effect-channels.js"
|
|
55
|
+
export { noMatchEffectBranch } from "./no-match-effect-branch.js"
|
|
56
|
+
export { noMatchVoidBranch } from "./no-match-void-branch.js"
|
|
57
|
+
export { noNestedEffectCall } from "./no-nested-effect-call.js"
|
|
58
|
+
export { noNestedEffectGen } from "./no-nested-effect-gen.js"
|
|
59
|
+
export { noNestedPipe } from "./no-nested-pipe.js"
|
|
60
|
+
export { noNewError } from "./no-new-error.js"
|
|
61
|
+
export { noNewPromise } from "./no-new-promise.js"
|
|
62
|
+
export { noNodeBuiltinImport } from "./no-node-builtin-import.js"
|
|
63
|
+
export { noOptionBooleanNormalization } from "./no-option-boolean-normalization.js"
|
|
64
|
+
export { noReturnInArrow } from "./no-return-in-arrow.js"
|
|
65
|
+
export { noReturnNull } from "./no-return-null.js"
|
|
66
|
+
export { noStringSentinelConst } from "./no-string-sentinel-const.js"
|
|
67
|
+
export { noTernary } from "./no-ternary.js"
|
|
68
|
+
export { noUnnecessaryArrowBlock } from "./no-unnecessary-arrow-block.js"
|
|
69
|
+
export { noUnnecessaryEffectGen } from "./no-unnecessary-effect-gen.js"
|
|
70
|
+
export { noUnnecessaryPipe } from "./no-unnecessary-pipe.js"
|
|
71
|
+
|
|
72
|
+
// --- Effect-context rules ---
|
|
73
|
+
export { noConsoleInEffect } from "./no-console-in-effect.js"
|
|
74
|
+
export { noDateInEffect } from "./no-date-in-effect.js"
|
|
75
|
+
export { noFetchInEffect } from "./no-fetch-in-effect.js"
|
|
76
|
+
export { noInlineRuntimeProvide } from "./no-inline-runtime-provide.js"
|
|
77
|
+
export { noJsonInEffect } from "./no-json-in-effect.js"
|
|
78
|
+
export { noPlatformGlobals } from "./no-platform-globals.js"
|
|
79
|
+
export { noProcessEnvInEffect } from "./no-process-env-in-effect.js"
|
|
80
|
+
export { noRandomInEffect } from "./no-random-in-effect.js"
|
|
81
|
+
export { noThrowInEffectGen } from "./no-throw-in-effect-gen.js"
|
|
82
|
+
export { noTimersInEffect } from "./no-timers-in-effect.js"
|
|
83
|
+
export { noTryCatchInEffectGen } from "./no-try-catch-in-effect-gen.js"
|