@timmo001/oxlint-rules 0.1.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 (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +102 -0
  3. package/THIRD_PARTY_NOTICES.md +10 -0
  4. package/dist/cli.js +78 -0
  5. package/dist/configs/effect.js +52 -0
  6. package/dist/configs/recommended.js +35 -0
  7. package/dist/effect/index.js +106 -0
  8. package/dist/upstream/anti-slop.js +1838 -0
  9. package/dist/upstream/effect.js +59 -0
  10. package/package.json +61 -0
  11. package/skills/add-oxlint-rule/SKILL.md +31 -0
  12. package/skills/install-timmo-oxlint-rules/SKILL.md +45 -0
  13. package/skills/install-timmo-oxlint-rules/scripts/copy.mjs +30 -0
  14. package/src/cli.ts +35 -0
  15. package/src/configs/effect.ts +20 -0
  16. package/src/configs/recommended.ts +29 -0
  17. package/src/effect/index.ts +12 -0
  18. package/src/effect/rules/no-try-catch-in-effect-generators.ts +140 -0
  19. package/src/install/copy.ts +77 -0
  20. package/src/jsr/effect-config.ts +7 -0
  21. package/src/jsr/effect.ts +7 -0
  22. package/src/jsr/recommended.ts +7 -0
  23. package/src/jsr/upstream-anti-slop.ts +7 -0
  24. package/src/jsr/upstream-effect.ts +7 -0
  25. package/src/upstream/anti-slop.ts +1 -0
  26. package/src/upstream/effect.ts +1 -0
  27. package/vendor/anti-slop/LICENSE +21 -0
  28. package/vendor/anti-slop/README.md +299 -0
  29. package/vendor/anti-slop/src/effect/index.ts +13 -0
  30. package/vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts +52 -0
  31. package/vendor/anti-slop/src/index.ts +41 -0
  32. package/vendor/anti-slop/src/rules/no-chained-type-assertions.ts +77 -0
  33. package/vendor/anti-slop/src/rules/no-conditional-empty-object-spread.ts +49 -0
  34. package/vendor/anti-slop/src/rules/no-known-value-widening.ts +427 -0
  35. package/vendor/anti-slop/src/rules/no-module-mocking.ts +91 -0
  36. package/vendor/anti-slop/src/rules/no-object-parameters.ts +83 -0
  37. package/vendor/anti-slop/src/rules/no-reflect-apply.ts +28 -0
  38. package/vendor/anti-slop/src/rules/no-reflect-get.ts +28 -0
  39. package/vendor/anti-slop/src/rules/no-runtime-typeof.ts +77 -0
  40. package/vendor/anti-slop/src/rules/no-shape-in-symbol-names.ts +46 -0
  41. package/vendor/anti-slop/src/rules/no-unknown-parameters.ts +69 -0
  42. package/vendor/anti-slop/src/rules/no-unknown-returns.ts +82 -0
  43. package/vendor/anti-slop/src/rules/no-unknown-type-aliases.ts +54 -0
  44. package/vendor/anti-slop/src/rules/no-unsafe-dictionary-type.ts +154 -0
  45. package/vendor/anti-slop/src/rules/no-widen-then-assert.ts +366 -0
  46. package/vendor/anti-slop/src/rules/require-safety-comment-for-type-assertion.ts +131 -0
  47. package/vendor/anti-slop/src/shared/dictionary-types.ts +515 -0
  48. package/vendor/anti-slop/src/shared/function-parameters.ts +49 -0
  49. package/vendor/anti-slop/src/shared/lexical-type-parameters.ts +61 -0
  50. package/vendor/anti-slop/src/shared/reflect-method.ts +35 -0
  51. package/vendor/anti-slop/src/shared/type-alias-resolution.ts +250 -0
@@ -0,0 +1,59 @@
1
+ // vendor/anti-slop/src/effect/index.ts
2
+ import { eslintCompatPlugin } from "@oxlint/plugins";
3
+
4
+ // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
5
+ import { defineRule } from "@oxlint/plugins";
6
+ var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
7
+ var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
8
+ function isProjectLocalImport(source) {
9
+ return source.startsWith("./") || source.startsWith("../");
10
+ }
11
+ function getImportedName(specifier) {
12
+ if (specifier.imported.type === "Identifier")
13
+ return specifier.imported.name;
14
+ return specifier.imported.value;
15
+ }
16
+ var noServiceConstructorImportsRule = defineRule({
17
+ meta: {
18
+ type: "problem",
19
+ docs: {
20
+ description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
21
+ },
22
+ messages: {
23
+ serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.'
24
+ }
25
+ },
26
+ create(context) {
27
+ const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
28
+ return {
29
+ ImportDeclaration(node) {
30
+ if (isTestFile || !isProjectLocalImport(node.source.value))
31
+ return;
32
+ for (const specifier of node.specifiers) {
33
+ if (specifier.type !== "ImportSpecifier")
34
+ continue;
35
+ const importedName = getImportedName(specifier);
36
+ if (!SERVICE_CONSTRUCTOR_NAME.test(importedName))
37
+ continue;
38
+ context.report({
39
+ node: specifier,
40
+ messageId: "serviceConstructorImport",
41
+ data: { name: importedName }
42
+ });
43
+ }
44
+ }
45
+ };
46
+ }
47
+ });
48
+
49
+ // vendor/anti-slop/src/effect/index.ts
50
+ var antiSlopEffectPlugin = eslintCompatPlugin({
51
+ meta: { name: "anti-slop-effect" },
52
+ rules: {
53
+ "no-service-constructor-imports": noServiceConstructorImportsRule
54
+ }
55
+ });
56
+ var effect_default = antiSlopEffectPlugin;
57
+ export {
58
+ effect_default as default
59
+ };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@timmo001/oxlint-rules",
3
+ "version": "0.1.0",
4
+ "description": "Shared Oxlint plugins and configs with optional Effect rules.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/timmo001/oxlint-rules.git"
10
+ },
11
+ "bin": {
12
+ "oxlint-rules": "./dist/cli.js"
13
+ },
14
+ "exports": {
15
+ "./upstream/anti-slop": "./dist/upstream/anti-slop.js",
16
+ "./upstream/effect": "./dist/upstream/effect.js",
17
+ "./effect": "./dist/effect/index.js",
18
+ "./configs/recommended": "./dist/configs/recommended.js",
19
+ "./configs/effect": "./dist/configs/effect.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src",
24
+ "!src/**/*.test.ts",
25
+ "vendor/anti-slop/src",
26
+ "vendor/anti-slop/LICENSE",
27
+ "!vendor/anti-slop/src/**/*.test.ts",
28
+ "skills",
29
+ "README.md",
30
+ "THIRD_PARTY_NOTICES.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "build": "bun run scripts/build.ts",
35
+ "check": "bun run build && bun run test && bun run lint && bun run typecheck && bun run format:check && bun run skills:validate",
36
+ "format": "prettier --write \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.md\" \"*.json\"",
37
+ "format:check": "prettier --check \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.md\" \"*.json\"",
38
+ "lint": "oxlint src scripts",
39
+ "skills:validate": "bunx skills-ref validate skills/install-timmo-oxlint-rules && bunx skills-ref validate skills/add-oxlint-rule",
40
+ "test": "node --experimental-strip-types src/effect/rules/no-try-catch-in-effect-generators.test.ts && bun test src/install/copy.test.ts",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "peerDependencies": {
44
+ "@oxlint/plugins": "1.81.0",
45
+ "oxlint": "1.81.0"
46
+ },
47
+ "devDependencies": {
48
+ "@oxlint/plugins": "1.81.0",
49
+ "@types/bun": "^1.3.14",
50
+ "oxlint": "1.81.0",
51
+ "prettier": "^3.9.5",
52
+ "skills-ref": "0.1.5",
53
+ "typescript": "^7.0.0"
54
+ },
55
+ "engines": {
56
+ "node": ">=20"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: add-oxlint-rule
3
+ description: >-
4
+ Create or revise a centrally maintained rule in @timmo001/oxlint-rules. Use
5
+ for requests to add an Oxlint anti-slop rule, change an existing central
6
+ rule, or promote a repository-specific lint preference into the shared
7
+ package.
8
+ ---
9
+
10
+ # Add An Oxlint Rule
11
+
12
+ 1. Treat the current repository as fixture context, not automatically as the
13
+ central package checkout.
14
+ 2. Search available writable checkouts by their Git remote for
15
+ `timmo001/oxlint-rules`. If none exists, ask where the user wants to clone or
16
+ fork it. Do not assume the user can write to the upstream account.
17
+ 3. Read the central repository guidance, relevant plugin registration, config,
18
+ nearby rules, tests, and README rule list.
19
+ 4. Define the narrow syntax contract. Add at least one failing fixture and one
20
+ valid fixture before implementing the rule.
21
+ 5. Put generic upstream-independent rules under the appropriate locally owned
22
+ plugin. Never modify `vendor/anti-slop`; propose an upstream contribution
23
+ separately when Dylan Mulroy's plugin should own the behaviour.
24
+ 6. Register the rule in its plugin and matching config, then update the README
25
+ rule list and behaviour description.
26
+ 7. Run `mise run check`, `mise run build`, `npm pack --dry-run`, and
27
+ `bunx jsr@0.14.3 publish --dry-run` in the central checkout.
28
+
29
+ Report the fixture contract, registration and docs changed, checks, and any
30
+ consumer rollout left for a separate stage. Do not publish or assume a local
31
+ machine path.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: install-timmo-oxlint-rules
3
+ description: >-
4
+ Install or copy @timmo001/oxlint-rules into a JavaScript or TypeScript
5
+ repository. Use when adding the shared anti-slop Oxlint config, enabling its
6
+ Effect rules, or replacing a local anti-slop copy.
7
+ ---
8
+
9
+ # Install Timmo Oxlint Rules
10
+
11
+ 1. Ask one structured question before changing files: `Package (Recommended)`
12
+ or `Copy rules`.
13
+ 2. Inspect the target's manifests, lockfiles, Oxlint config, repository
14
+ instructions, and normal checks. Use the current working directory unless
15
+ the user names another target.
16
+ 3. Preserve existing ignores, overrides, plugins, and repository-owned rules.
17
+ Confirm `oxlint` and `@oxlint/plugins` use the same exact version supported
18
+ by the selected package version.
19
+
20
+ ## Package
21
+
22
+ 1. Ask whether to use npmjs.org or JSR.
23
+ 2. Detect Bun, npm, pnpm, or Yarn from the target's package manager declaration
24
+ and lockfile. Add an exact development dependency through that package
25
+ manager.
26
+ 3. Extend `@timmo001/oxlint-rules/configs/recommended`. Use `/configs/effect`
27
+ instead only when `effect` is a direct dependency or the user explicitly
28
+ requests it.
29
+ 4. Keep dependency and config edits visible. Do not delegate them to a script.
30
+
31
+ ## Copy rules
32
+
33
+ 1. Ask for a repository-relative destination. Do not assume a personal
34
+ filesystem layout.
35
+ 2. If the destination exists, compare it with the proposed source and explain
36
+ meaningful differences before asking whether replacement is intended.
37
+ 3. Run `node scripts/copy.mjs <bun|npm|pnpm|yarn> <destination>`. Add `--force`
38
+ only after explicit replacement approval.
39
+ 4. Register the three entry points printed by the command as `anti-slop`,
40
+ `anti-slop-effect`, and `timmo-effect`. Enable `timmo-effect` only for direct
41
+ Effect use or an explicit request.
42
+
43
+ Run the target repository's normal lint, typecheck, tests, and build. Report
44
+ package-manager changes, preserved local configuration, enabled rule groups,
45
+ and checks.
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+
4
+ const [runner, ...copyArguments] = process.argv.slice(2);
5
+ const packageName = "@timmo001/oxlint-rules";
6
+ const commands = {
7
+ bun: ["bunx", packageName, "copy", ...copyArguments],
8
+ npm: [
9
+ "npm",
10
+ "exec",
11
+ "--yes",
12
+ `--package=${packageName}`,
13
+ "--",
14
+ "oxlint-rules",
15
+ "copy",
16
+ ...copyArguments,
17
+ ],
18
+ pnpm: ["pnpm", "dlx", packageName, "copy", ...copyArguments],
19
+ yarn: ["yarn", "dlx", packageName, "copy", ...copyArguments],
20
+ };
21
+
22
+ const command = commands[runner];
23
+ if (!command) {
24
+ console.error("Usage: copy.mjs <bun|npm|pnpm|yarn> <destination> [--force]");
25
+ process.exit(1);
26
+ }
27
+
28
+ const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
29
+ if (result.error) throw result.error;
30
+ process.exit(result.status ?? 1);
package/src/cli.ts ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+
4
+ import { copyRules, DEFAULT_COPY_DESTINATION } from "./install/copy.ts";
5
+
6
+ function usage() {
7
+ return "Usage: oxlint-rules copy [destination] [--force]";
8
+ }
9
+
10
+ const [command, ...arguments_] = process.argv.slice(2);
11
+ if (command === "--help" || command === "-h") {
12
+ console.log(usage());
13
+ } else if (command !== "copy") {
14
+ console.error(usage());
15
+ process.exitCode = 1;
16
+ } else {
17
+ const force = arguments_.includes("--force");
18
+ const positional = arguments_.filter((argument) => argument !== "--force");
19
+ if (positional.length > 1) {
20
+ console.error(usage());
21
+ process.exitCode = 1;
22
+ } else {
23
+ try {
24
+ const destination = resolve(positional[0] ?? DEFAULT_COPY_DESTINATION);
25
+ const entries = await copyRules(destination, { force });
26
+ console.log("Copied Oxlint plugins:");
27
+ console.log(` anti-slop: ${entries.antiSlop}`);
28
+ console.log(` anti-slop-effect: ${entries.antiSlopEffect}`);
29
+ console.log(` timmo-effect: ${entries.timmoEffect}`);
30
+ } catch (error) {
31
+ console.error(error instanceof Error ? error.message : String(error));
32
+ process.exitCode = 1;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from "oxlint";
2
+
3
+ import recommended from "./recommended.ts";
4
+
5
+ const effect = defineConfig({
6
+ extends: [recommended],
7
+ jsPlugins: [
8
+ {
9
+ name: "anti-slop-effect",
10
+ specifier: "@timmo001/oxlint-rules/upstream/effect",
11
+ },
12
+ { name: "timmo-effect", specifier: "@timmo001/oxlint-rules/effect" },
13
+ ],
14
+ rules: {
15
+ "anti-slop-effect/no-service-constructor-imports": "error",
16
+ "timmo-effect/no-try-catch-in-effect-generators": "error",
17
+ },
18
+ });
19
+
20
+ export default effect;
@@ -0,0 +1,29 @@
1
+ import { defineConfig } from "oxlint";
2
+
3
+ const recommended = defineConfig({
4
+ jsPlugins: [
5
+ {
6
+ name: "anti-slop",
7
+ specifier: "@timmo001/oxlint-rules/upstream/anti-slop",
8
+ },
9
+ ],
10
+ rules: {
11
+ "anti-slop/no-chained-type-assertions": "error",
12
+ "anti-slop/no-conditional-empty-object-spread": "error",
13
+ "anti-slop/no-known-value-widening": "error",
14
+ "anti-slop/no-module-mocking": "error",
15
+ "anti-slop/no-object-parameters": "error",
16
+ "anti-slop/no-reflect-apply": "error",
17
+ "anti-slop/no-reflect-get": "error",
18
+ "anti-slop/no-runtime-typeof": "error",
19
+ "anti-slop/no-shape-in-symbol-names": "error",
20
+ "anti-slop/no-unknown-parameters": "error",
21
+ "anti-slop/no-unknown-returns": "error",
22
+ "anti-slop/no-unknown-type-aliases": "error",
23
+ "anti-slop/no-unsafe-dictionary-type": "error",
24
+ "anti-slop/no-widen-then-assert": "error",
25
+ "anti-slop/require-safety-comment-for-type-assertion": "error",
26
+ },
27
+ });
28
+
29
+ export default recommended;
@@ -0,0 +1,12 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+
3
+ import { noTryCatchInEffectGeneratorsRule } from "./rules/no-try-catch-in-effect-generators.ts";
4
+
5
+ const timmoEffectPlugin = eslintCompatPlugin({
6
+ meta: { name: "timmo-effect" },
7
+ rules: {
8
+ "no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule,
9
+ },
10
+ });
11
+
12
+ export default timmoEffectPlugin;
@@ -0,0 +1,140 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+
3
+ import type { ESTree, Scope, SourceCode, Variable } from "@oxlint/plugins";
4
+
5
+ type FunctionNode = ESTree.Function | ESTree.ArrowFunctionExpression;
6
+
7
+ function resolveVariable(
8
+ sourceCode: SourceCode,
9
+ identifier: ESTree.IdentifierReference,
10
+ ): Variable | null {
11
+ let scope: Scope | null = sourceCode.getScope(identifier);
12
+ while (scope) {
13
+ const variable = scope.set.get(identifier.name);
14
+ if (variable) return variable;
15
+ scope = scope.upper;
16
+ }
17
+ return null;
18
+ }
19
+
20
+ function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
21
+ let current: ESTree.Node | null = node.parent;
22
+ while (current) {
23
+ if (
24
+ current.type === "FunctionDeclaration" ||
25
+ current.type === "FunctionExpression" ||
26
+ current.type === "ArrowFunctionExpression"
27
+ ) {
28
+ return current;
29
+ }
30
+ current = current.parent;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function staticMemberName(node: ESTree.Expression): string | null {
36
+ if (node.type !== "MemberExpression" || node.computed) return null;
37
+ return node.property.type === "Identifier" ? node.property.name : null;
38
+ }
39
+
40
+ function isEffectImport(
41
+ sourceCode: SourceCode,
42
+ identifier: ESTree.IdentifierReference,
43
+ namespace: boolean,
44
+ ): boolean {
45
+ const variable = resolveVariable(sourceCode, identifier);
46
+ return (
47
+ variable?.defs.some((definition) => {
48
+ if (
49
+ definition.type !== "ImportBinding" ||
50
+ definition.parent?.type !== "ImportDeclaration" ||
51
+ definition.parent.source.value !== "effect"
52
+ ) {
53
+ return false;
54
+ }
55
+ if (namespace) return definition.node.type === "ImportNamespaceSpecifier";
56
+ if (definition.node.type !== "ImportSpecifier") return false;
57
+ const imported = definition.node.imported;
58
+ return (
59
+ (imported.type === "Identifier" ? imported.name : imported.value) ===
60
+ "Effect"
61
+ );
62
+ }) ?? false
63
+ );
64
+ }
65
+
66
+ function isEffectMethod(
67
+ sourceCode: SourceCode,
68
+ node: ESTree.Expression,
69
+ method: "fn" | "gen",
70
+ ): boolean {
71
+ if (staticMemberName(node) !== method || node.type !== "MemberExpression") {
72
+ return false;
73
+ }
74
+ const object = node.object;
75
+ if (object.type === "Identifier") {
76
+ return isEffectImport(sourceCode, object, false);
77
+ }
78
+ if (
79
+ staticMemberName(object) !== "Effect" ||
80
+ object.type !== "MemberExpression" ||
81
+ object.object.type !== "Identifier"
82
+ ) {
83
+ return false;
84
+ }
85
+ return isEffectImport(sourceCode, object.object, true);
86
+ }
87
+
88
+ function isDirectArgument(
89
+ owner: FunctionNode,
90
+ call: ESTree.CallExpression,
91
+ ): boolean {
92
+ return call.arguments.some((argument) => argument === owner);
93
+ }
94
+
95
+ function isRecognisedEffectGenerator(
96
+ sourceCode: SourceCode,
97
+ owner: FunctionNode,
98
+ ): boolean {
99
+ const parent = owner.parent;
100
+ if (parent.type !== "CallExpression" || !isDirectArgument(owner, parent)) {
101
+ return false;
102
+ }
103
+ if (isEffectMethod(sourceCode, parent.callee, "gen")) return true;
104
+
105
+ const factoryCall = parent.callee;
106
+ return (
107
+ factoryCall.type === "CallExpression" &&
108
+ isEffectMethod(sourceCode, factoryCall.callee, "fn")
109
+ );
110
+ }
111
+
112
+ /** Keep expected failures in the Effect error channel inside Effect generators. */
113
+ export const noTryCatchInEffectGeneratorsRule = defineRule({
114
+ meta: {
115
+ type: "problem",
116
+ docs: {
117
+ description:
118
+ "Disallow synchronous try/catch owned by recognised Effect generator callbacks.",
119
+ },
120
+ messages: {
121
+ useEffectErrorChannel:
122
+ "Keep expected failures in the Effect error channel. Use Effect.try for synchronous throwing work, Effect.tryPromise for asynchronous throwing work, Effect-returning schema APIs for decoding, and Effect recovery combinators for recovery.",
123
+ },
124
+ },
125
+ create(context) {
126
+ return {
127
+ TryStatement(node) {
128
+ if (!node.handler) return;
129
+ const owner = nearestEnclosingFunction(node);
130
+ if (
131
+ !owner?.generator ||
132
+ !isRecognisedEffectGenerator(context.sourceCode, owner)
133
+ ) {
134
+ return;
135
+ }
136
+ context.report({ node, messageId: "useEffectErrorChannel" });
137
+ },
138
+ };
139
+ },
140
+ });
@@ -0,0 +1,77 @@
1
+ import { cp, mkdir, readdir, rm } from "node:fs/promises";
2
+ import { dirname, join, relative, resolve, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ export const DEFAULT_COPY_DESTINATION = "tools/oxlint/timmo-rules";
6
+
7
+ export interface CopyRulesOptions {
8
+ readonly force?: boolean;
9
+ readonly sourceRoot?: string;
10
+ }
11
+
12
+ export interface CopiedRuleEntryPoints {
13
+ readonly antiSlop: string;
14
+ readonly antiSlopEffect: string;
15
+ readonly timmoEffect: string;
16
+ }
17
+
18
+ function packageRoot() {
19
+ const current = dirname(fileURLToPath(import.meta.url));
20
+ return current.endsWith(join("src", "install"))
21
+ ? resolve(current, "../..")
22
+ : resolve(current, "..");
23
+ }
24
+
25
+ async function copyTree(source: string, destination: string) {
26
+ await cp(source, destination, {
27
+ recursive: true,
28
+ filter: (path) =>
29
+ !path.endsWith(".test.ts") && !path.includes(`${join("", ".git")}`),
30
+ });
31
+ }
32
+
33
+ export async function copyRules(
34
+ destination: string,
35
+ options: CopyRulesOptions = {},
36
+ ): Promise<CopiedRuleEntryPoints> {
37
+ const target = resolve(destination);
38
+ const entries = await readdir(dirname(target), { withFileTypes: true }).catch(
39
+ () => [],
40
+ );
41
+ if (entries.some((entry) => entry.name === target.split(/[\\/]/u).at(-1))) {
42
+ if (!options.force) {
43
+ throw new Error(
44
+ `Destination already exists: ${target}. Pass --force to replace it.`,
45
+ );
46
+ }
47
+ await rm(target, { force: true, recursive: true });
48
+ }
49
+
50
+ const root = options.sourceRoot ?? packageRoot();
51
+ await mkdir(join(target, "upstream"), { recursive: true });
52
+ await copyTree(
53
+ join(root, "vendor/anti-slop/src"),
54
+ join(target, "upstream/anti-slop"),
55
+ );
56
+ await cp(
57
+ join(root, "vendor/anti-slop/LICENSE"),
58
+ join(target, "upstream/anti-slop/LICENSE"),
59
+ );
60
+ await rm(join(target, "upstream/anti-slop/effect"), {
61
+ force: true,
62
+ recursive: true,
63
+ });
64
+ await copyTree(
65
+ join(root, "vendor/anti-slop/src/effect"),
66
+ join(target, "upstream/effect"),
67
+ );
68
+ await copyTree(join(root, "src/effect"), join(target, "effect"));
69
+
70
+ const displayRoot = relative(process.cwd(), target) || ".";
71
+ const entryPoint = (path: string) => `./${path.split(sep).join("/")}`;
72
+ return {
73
+ antiSlop: entryPoint(join(displayRoot, "upstream/anti-slop/index.ts")),
74
+ antiSlopEffect: entryPoint(join(displayRoot, "upstream/effect/index.ts")),
75
+ timmoEffect: entryPoint(join(displayRoot, "effect/index.ts")),
76
+ };
77
+ }
@@ -0,0 +1,7 @@
1
+ import type { OxlintConfig } from "oxlint";
2
+
3
+ import bundledConfig from "../../dist/configs/effect.js";
4
+
5
+ const config: OxlintConfig = bundledConfig;
6
+
7
+ export default config;
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from "@oxlint/plugins";
2
+
3
+ import bundledPlugin from "../../dist/effect/index.js";
4
+
5
+ const plugin: Plugin = bundledPlugin;
6
+
7
+ export default plugin;
@@ -0,0 +1,7 @@
1
+ import type { OxlintConfig } from "oxlint";
2
+
3
+ import bundledConfig from "../../dist/configs/recommended.js";
4
+
5
+ const config: OxlintConfig = bundledConfig;
6
+
7
+ export default config;
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from "@oxlint/plugins";
2
+
3
+ import bundledPlugin from "../../dist/upstream/anti-slop.js";
4
+
5
+ const plugin: Plugin = bundledPlugin;
6
+
7
+ export default plugin;
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from "@oxlint/plugins";
2
+
3
+ import bundledPlugin from "../../dist/upstream/effect.js";
4
+
5
+ const plugin: Plugin = bundledPlugin;
6
+
7
+ export default plugin;
@@ -0,0 +1 @@
1
+ export { default } from "../../vendor/anti-slop/src/index.ts";
@@ -0,0 +1 @@
1
+ export { default } from "../../vendor/anti-slop/src/effect/index.ts";
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dillon Mulroy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.