@timmo001/oxlint-rules 0.1.3 → 0.2.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 +12 -4
- package/dist/cli.js +2120 -0
- package/dist/configs/effect.js +2076 -20
- package/dist/configs/recommended.js +1912 -16
- package/dist/generic/index.js +70 -0
- package/package.json +8 -4
- package/skills/add-oxlint-rule/SKILL.md +4 -2
- package/skills/install-timmo-oxlint-rules/SKILL.md +8 -6
- package/src/generic/index.ts +12 -0
- package/src/generic/rules/prefer-event-parameter-type.ts +83 -0
- package/src/cli.ts +0 -35
- package/src/configs/effect.ts +0 -20
- package/src/configs/recommended.ts +0 -29
- package/src/install/copy.ts +0 -77
- package/src/jsr/effect-config.ts +0 -7
- package/src/jsr/effect.ts +0 -7
- package/src/jsr/recommended.ts +0 -7
- package/src/jsr/upstream-anti-slop.ts +0 -7
- package/src/jsr/upstream-effect.ts +0 -7
- package/src/upstream/anti-slop.ts +0 -1
- package/src/upstream/effect.ts +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/generic/index.ts
|
|
2
|
+
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
3
|
+
|
|
4
|
+
// src/generic/rules/prefer-event-parameter-type.ts
|
|
5
|
+
import { defineRule } from "@oxlint/plugins";
|
|
6
|
+
function nearestEnclosingFunction(node) {
|
|
7
|
+
let current = node.parent;
|
|
8
|
+
while (current) {
|
|
9
|
+
if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
12
|
+
current = current.parent;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function assertedEventParameter(node) {
|
|
17
|
+
const expression = node.expression;
|
|
18
|
+
if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const parameterName = expression.object.name;
|
|
22
|
+
const property = expression.property.name;
|
|
23
|
+
const owner = nearestEnclosingFunction(node);
|
|
24
|
+
if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
parameter: parameterName,
|
|
29
|
+
property
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
var preferEventParameterTypeRule = defineRule({
|
|
33
|
+
meta: {
|
|
34
|
+
type: "suggestion",
|
|
35
|
+
docs: {
|
|
36
|
+
description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
|
|
37
|
+
},
|
|
38
|
+
messages: {
|
|
39
|
+
typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
create(context) {
|
|
43
|
+
const checkAssertion = (node) => {
|
|
44
|
+
const eventParameter = assertedEventParameter(node);
|
|
45
|
+
if (!eventParameter)
|
|
46
|
+
return;
|
|
47
|
+
context.report({
|
|
48
|
+
node,
|
|
49
|
+
messageId: "typeEventParameter",
|
|
50
|
+
data: eventParameter
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
TSAsExpression: checkAssertion,
|
|
55
|
+
TSTypeAssertion: checkAssertion
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// src/generic/index.ts
|
|
61
|
+
var timmoPlugin = eslintCompatPlugin({
|
|
62
|
+
meta: { name: "timmo" },
|
|
63
|
+
rules: {
|
|
64
|
+
"prefer-event-parameter-type": preferEventParameterTypeRule
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
var generic_default = timmoPlugin;
|
|
68
|
+
export {
|
|
69
|
+
generic_default as default
|
|
70
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmo001/oxlint-rules",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared Oxlint plugins and configs with optional Effect rules.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -14,17 +14,21 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
"./upstream/anti-slop": "./dist/upstream/anti-slop.js",
|
|
16
16
|
"./upstream/effect": "./dist/upstream/effect.js",
|
|
17
|
+
"./generic": "./dist/generic/index.js",
|
|
17
18
|
"./effect": "./dist/effect/index.js",
|
|
18
19
|
"./configs/recommended": "./dist/configs/recommended.js",
|
|
19
20
|
"./configs/effect": "./dist/configs/effect.js"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
|
23
|
-
"src",
|
|
24
|
-
"
|
|
24
|
+
"src/effect",
|
|
25
|
+
"src/generic",
|
|
26
|
+
"!src/generic/**/*.test.ts",
|
|
27
|
+
"!src/effect/**/*.test.ts",
|
|
25
28
|
"vendor/anti-slop/src",
|
|
26
29
|
"vendor/anti-slop/LICENSE",
|
|
27
30
|
"!vendor/anti-slop/src/**/*.test.ts",
|
|
31
|
+
"!vendor/anti-slop/README.md",
|
|
28
32
|
"skills",
|
|
29
33
|
"README.md",
|
|
30
34
|
"THIRD_PARTY_NOTICES.md",
|
|
@@ -37,7 +41,7 @@
|
|
|
37
41
|
"format:check": "prettier --check \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.md\" \"*.json\"",
|
|
38
42
|
"lint": "oxlint src scripts",
|
|
39
43
|
"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
|
|
44
|
+
"test": "node --experimental-strip-types scripts/test.ts",
|
|
41
45
|
"typecheck": "tsc --noEmit"
|
|
42
46
|
},
|
|
43
47
|
"peerDependencies": {
|
|
@@ -21,8 +21,10 @@ description: >-
|
|
|
21
21
|
5. Put generic upstream-independent rules under the appropriate locally owned
|
|
22
22
|
plugin. Never modify `vendor/anti-slop`; propose an upstream contribution
|
|
23
23
|
separately when Dylan Mulroy's plugin should own the behaviour.
|
|
24
|
-
6. Register the rule in its plugin
|
|
25
|
-
|
|
24
|
+
6. Register the rule in its plugin. Verify the matching config and copy command
|
|
25
|
+
discover it from the plugin's `rules` map, then update the README rule list
|
|
26
|
+
and behaviour description. Keep skills workflow-only; do not duplicate rule
|
|
27
|
+
or plugin inventories or counts in them.
|
|
26
28
|
7. Run `mise run check`, `mise run build`, `npm pack --dry-run`, and
|
|
27
29
|
`bunx jsr@0.14.3 publish --dry-run` in the central checkout.
|
|
28
30
|
|
|
@@ -23,9 +23,9 @@ description: >-
|
|
|
23
23
|
2. Detect Bun, npm, pnpm, or Yarn from the target's package manager declaration
|
|
24
24
|
and lockfile. Add an exact development dependency through that package
|
|
25
25
|
manager.
|
|
26
|
-
3. Extend `@timmo001/oxlint-rules/configs/recommended`.
|
|
27
|
-
|
|
28
|
-
requests it.
|
|
26
|
+
3. Extend `@timmo001/oxlint-rules/configs/recommended`. The config owns plugin
|
|
27
|
+
registration and recommended severities. Use `/configs/effect` instead only
|
|
28
|
+
when `effect` is a direct dependency or the user explicitly requests it.
|
|
29
29
|
4. Keep dependency and config edits visible. Do not delegate them to a script.
|
|
30
30
|
|
|
31
31
|
## Copy rules
|
|
@@ -36,9 +36,11 @@ description: >-
|
|
|
36
36
|
meaningful differences before asking whether replacement is intended.
|
|
37
37
|
3. Run `node scripts/copy.mjs <bun|npm|pnpm|yarn> <destination>`. Add `--force`
|
|
38
38
|
only after explicit replacement approval.
|
|
39
|
-
4. Register
|
|
40
|
-
|
|
41
|
-
Effect use or an explicit request.
|
|
39
|
+
4. Register every plugin entry point printed by the command. Merge every printed
|
|
40
|
+
general rule setting into the target config. Merge every printed Effect rule
|
|
41
|
+
setting only for direct Effect use or an explicit request. Treat the command
|
|
42
|
+
output as authoritative rather than maintaining plugin or rule inventories
|
|
43
|
+
in this skill.
|
|
42
44
|
|
|
43
45
|
Run the target repository's normal lint, typecheck, tests, and build. Report
|
|
44
46
|
package-manager changes, preserved local configuration, enabled rule groups,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
2
|
+
|
|
3
|
+
import { preferEventParameterTypeRule } from "./rules/prefer-event-parameter-type.ts";
|
|
4
|
+
|
|
5
|
+
const timmoPlugin = eslintCompatPlugin({
|
|
6
|
+
meta: { name: "timmo" },
|
|
7
|
+
rules: {
|
|
8
|
+
"prefer-event-parameter-type": preferEventParameterTypeRule,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default timmoPlugin;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
|
|
3
|
+
import type { ESTree } from "@oxlint/plugins";
|
|
4
|
+
|
|
5
|
+
type FunctionNode = ESTree.Function | ESTree.ArrowFunctionExpression;
|
|
6
|
+
type TypeAssertion = ESTree.TSAsExpression | ESTree.TSTypeAssertion;
|
|
7
|
+
|
|
8
|
+
function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
|
|
9
|
+
let current: ESTree.Node | null = node.parent;
|
|
10
|
+
while (current) {
|
|
11
|
+
if (
|
|
12
|
+
current.type === "FunctionDeclaration" ||
|
|
13
|
+
current.type === "FunctionExpression" ||
|
|
14
|
+
current.type === "ArrowFunctionExpression"
|
|
15
|
+
) {
|
|
16
|
+
return current;
|
|
17
|
+
}
|
|
18
|
+
current = current.parent;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertedEventParameter(node: TypeAssertion): {
|
|
24
|
+
readonly parameter: string;
|
|
25
|
+
readonly property: "currentTarget" | "target";
|
|
26
|
+
} | null {
|
|
27
|
+
const expression = node.expression;
|
|
28
|
+
if (
|
|
29
|
+
expression.type !== "MemberExpression" ||
|
|
30
|
+
expression.computed ||
|
|
31
|
+
expression.object.type !== "Identifier" ||
|
|
32
|
+
expression.property.type !== "Identifier" ||
|
|
33
|
+
(expression.property.name !== "currentTarget" &&
|
|
34
|
+
expression.property.name !== "target")
|
|
35
|
+
) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const parameterName = expression.object.name;
|
|
39
|
+
const property = expression.property.name;
|
|
40
|
+
const owner = nearestEnclosingFunction(node);
|
|
41
|
+
if (
|
|
42
|
+
!owner?.params.some(
|
|
43
|
+
(parameter) =>
|
|
44
|
+
parameter.type === "Identifier" && parameter.name === parameterName,
|
|
45
|
+
)
|
|
46
|
+
) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
parameter: parameterName,
|
|
51
|
+
property,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Prefer expressing an event target type in its handler parameter signature. */
|
|
56
|
+
export const preferEventParameterTypeRule = defineRule({
|
|
57
|
+
meta: {
|
|
58
|
+
type: "suggestion",
|
|
59
|
+
docs: {
|
|
60
|
+
description:
|
|
61
|
+
"Prefer typing event target properties in the handler parameter instead of asserting them at use sites.",
|
|
62
|
+
},
|
|
63
|
+
messages: {
|
|
64
|
+
typeEventParameter:
|
|
65
|
+
"Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site.",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
create(context) {
|
|
69
|
+
const checkAssertion = (node: TypeAssertion) => {
|
|
70
|
+
const eventParameter = assertedEventParameter(node);
|
|
71
|
+
if (!eventParameter) return;
|
|
72
|
+
context.report({
|
|
73
|
+
node,
|
|
74
|
+
messageId: "typeEventParameter",
|
|
75
|
+
data: eventParameter,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
TSAsExpression: checkAssertion,
|
|
80
|
+
TSTypeAssertion: checkAssertion,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
package/src/cli.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
}
|
package/src/configs/effect.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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;
|
|
@@ -1,29 +0,0 @@
|
|
|
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;
|
package/src/install/copy.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
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
|
-
}
|
package/src/jsr/effect-config.ts
DELETED
package/src/jsr/effect.ts
DELETED
package/src/jsr/recommended.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "../../vendor/anti-slop/src/index.ts";
|
package/src/upstream/effect.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "../../vendor/anti-slop/src/effect/index.ts";
|