@timmo001/oxlint-rules 0.3.3 → 0.3.5
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 +11 -0
- package/package.json +9 -9
- package/skills/install-timmo-oxlint-rules/SKILL.md +3 -1
- package/skills/install-timmo-oxlint-rules/scripts/copy.mjs +5 -0
- package/src/effect/rules/no-try-catch-in-effect-generators.ts +20 -0
- package/src/generic/rules/prefer-event-parameter-type.ts +10 -0
package/README.md
CHANGED
|
@@ -87,6 +87,17 @@ npm pack --dry-run
|
|
|
87
87
|
bunx jsr publish --dry-run
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
`bun run lint` builds the local plugins before linting all maintained code,
|
|
91
|
+
including tests, tooling, the root config and packaged skill scripts. The root
|
|
92
|
+
config extends the local `recommended-effect` source config, enabling every
|
|
93
|
+
registered rule in `anti-slop`, `timmo`, `anti-slop-effect` and `timmo-effect`.
|
|
94
|
+
Plugin imports resolve through this package's exports to the fresh `dist` build.
|
|
95
|
+
|
|
96
|
+
Lint disables nested configs so the vendored submodule's config cannot override
|
|
97
|
+
the root exclusions. Generated output, vendored source and agent directories
|
|
98
|
+
remain excluded. RuleTester fixture strings retain their intentional violations;
|
|
99
|
+
the surrounding test code is linted with the full rule set.
|
|
100
|
+
|
|
90
101
|
Advance `vendor/anti-slop` only by updating its Git submodule commit. Keep its
|
|
91
102
|
source and MIT licence unchanged, then run the full package checks so both
|
|
92
103
|
upstream exports and composed configs are verified.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmo001/oxlint-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Shared Oxlint plugins and configs with optional Effect rules.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -36,22 +36,22 @@
|
|
|
36
36
|
],
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "bun run scripts/build.ts",
|
|
39
|
-
"check": "bun run
|
|
40
|
-
"format": "prettier --write \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.md\" \"*.json\"",
|
|
41
|
-
"format:check": "prettier --check \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.md\" \"*.json\"",
|
|
42
|
-
"lint": "oxlint
|
|
39
|
+
"check": "bun run lint && bun run test && bun run typecheck && bun run format:check && bun run skills:validate",
|
|
40
|
+
"format": "prettier --write \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.ts\" \"*.md\" \"*.json\"",
|
|
41
|
+
"format:check": "prettier --check \"{src,scripts}/**/*.ts\" \"skills/**/*.{md,mjs}\" \"*.ts\" \"*.md\" \"*.json\"",
|
|
42
|
+
"lint": "bun run build && oxlint --disable-nested-config .",
|
|
43
43
|
"skills:validate": "bunx skills-ref validate skills/install-timmo-oxlint-rules && bunx skills-ref validate skills/add-oxlint-rule && bunx skills-ref validate skills/release-oxlint-rules",
|
|
44
44
|
"test": "node --experimental-strip-types scripts/test.ts",
|
|
45
45
|
"typecheck": "tsc --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@oxlint/plugins": "1.
|
|
49
|
-
"oxlint": "1.
|
|
48
|
+
"@oxlint/plugins": "1.83.0",
|
|
49
|
+
"oxlint": "1.83.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@oxlint/plugins": "1.
|
|
52
|
+
"@oxlint/plugins": "1.83.0",
|
|
53
53
|
"@types/bun": "^1.3.14",
|
|
54
|
-
"oxlint": "1.
|
|
54
|
+
"oxlint": "1.83.0",
|
|
55
55
|
"prettier": "^3.9.5",
|
|
56
56
|
"skills-ref": "0.1.5",
|
|
57
57
|
"typescript": "^7.0.0"
|
|
@@ -43,6 +43,8 @@ description: >-
|
|
|
43
43
|
output as authoritative rather than maintaining plugin or rule inventories
|
|
44
44
|
in this skill.
|
|
45
45
|
|
|
46
|
-
Run the target repository's
|
|
46
|
+
Run the target repository's lint command to verify the configuration loads, plus
|
|
47
|
+
any checks explicitly required by that repository. Run typechecks, tests, or a
|
|
48
|
+
build only when code or dependencies changed in a way those checks cover. Report
|
|
47
49
|
package-manager changes, preserved local configuration, enabled rule groups,
|
|
48
50
|
and checks.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
|
|
4
4
|
const [runner, ...copyArguments] = process.argv.slice(2);
|
|
5
|
+
|
|
5
6
|
const packageName = "@timmo001/oxlint-rules";
|
|
7
|
+
|
|
6
8
|
const commands = {
|
|
7
9
|
bun: ["bunx", packageName, "copy", ...copyArguments],
|
|
8
10
|
npm: [
|
|
@@ -20,11 +22,14 @@ const commands = {
|
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
const command = commands[runner];
|
|
25
|
+
|
|
23
26
|
if (!command) {
|
|
24
27
|
console.error("Usage: copy.mjs <bun|npm|pnpm|yarn> <destination> [--force]");
|
|
25
28
|
process.exit(1);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
|
|
32
|
+
|
|
29
33
|
if (result.error) throw result.error;
|
|
34
|
+
|
|
30
35
|
process.exit(result.status ?? 1);
|
|
@@ -9,16 +9,20 @@ function resolveVariable(
|
|
|
9
9
|
identifier: ESTree.IdentifierReference,
|
|
10
10
|
): Variable | null {
|
|
11
11
|
let scope: Scope | null = sourceCode.getScope(identifier);
|
|
12
|
+
|
|
12
13
|
while (scope) {
|
|
13
14
|
const variable = scope.set.get(identifier.name);
|
|
15
|
+
|
|
14
16
|
if (variable) return variable;
|
|
15
17
|
scope = scope.upper;
|
|
16
18
|
}
|
|
19
|
+
|
|
17
20
|
return null;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
|
|
21
24
|
let current: ESTree.Node | null = node.parent;
|
|
25
|
+
|
|
22
26
|
while (current) {
|
|
23
27
|
if (
|
|
24
28
|
current.type === "FunctionDeclaration" ||
|
|
@@ -27,13 +31,16 @@ function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
|
|
|
27
31
|
) {
|
|
28
32
|
return current;
|
|
29
33
|
}
|
|
34
|
+
|
|
30
35
|
current = current.parent;
|
|
31
36
|
}
|
|
37
|
+
|
|
32
38
|
return null;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
function staticMemberName(node: ESTree.Expression): string | null {
|
|
36
42
|
if (node.type !== "MemberExpression" || node.computed) return null;
|
|
43
|
+
|
|
37
44
|
return node.property.type === "Identifier" ? node.property.name : null;
|
|
38
45
|
}
|
|
39
46
|
|
|
@@ -43,6 +50,7 @@ function isEffectImport(
|
|
|
43
50
|
namespace: boolean,
|
|
44
51
|
): boolean {
|
|
45
52
|
const variable = resolveVariable(sourceCode, identifier);
|
|
53
|
+
|
|
46
54
|
return (
|
|
47
55
|
variable?.defs.some((definition) => {
|
|
48
56
|
if (
|
|
@@ -52,9 +60,12 @@ function isEffectImport(
|
|
|
52
60
|
) {
|
|
53
61
|
return false;
|
|
54
62
|
}
|
|
63
|
+
|
|
55
64
|
if (namespace) return definition.node.type === "ImportNamespaceSpecifier";
|
|
65
|
+
|
|
56
66
|
if (definition.node.type !== "ImportSpecifier") return false;
|
|
57
67
|
const imported = definition.node.imported;
|
|
68
|
+
|
|
58
69
|
return (
|
|
59
70
|
(imported.type === "Identifier" ? imported.name : imported.value) ===
|
|
60
71
|
"Effect"
|
|
@@ -71,10 +82,13 @@ function isEffectMethod(
|
|
|
71
82
|
if (staticMemberName(node) !== method || node.type !== "MemberExpression") {
|
|
72
83
|
return false;
|
|
73
84
|
}
|
|
85
|
+
|
|
74
86
|
const object = node.object;
|
|
87
|
+
|
|
75
88
|
if (object.type === "Identifier") {
|
|
76
89
|
return isEffectImport(sourceCode, object, false);
|
|
77
90
|
}
|
|
91
|
+
|
|
78
92
|
if (
|
|
79
93
|
staticMemberName(object) !== "Effect" ||
|
|
80
94
|
object.type !== "MemberExpression" ||
|
|
@@ -82,6 +96,7 @@ function isEffectMethod(
|
|
|
82
96
|
) {
|
|
83
97
|
return false;
|
|
84
98
|
}
|
|
99
|
+
|
|
85
100
|
return isEffectImport(sourceCode, object.object, true);
|
|
86
101
|
}
|
|
87
102
|
|
|
@@ -97,12 +112,15 @@ function isRecognisedEffectGenerator(
|
|
|
97
112
|
owner: FunctionNode,
|
|
98
113
|
): boolean {
|
|
99
114
|
const parent = owner.parent;
|
|
115
|
+
|
|
100
116
|
if (parent.type !== "CallExpression" || !isDirectArgument(owner, parent)) {
|
|
101
117
|
return false;
|
|
102
118
|
}
|
|
119
|
+
|
|
103
120
|
if (isEffectMethod(sourceCode, parent.callee, "gen")) return true;
|
|
104
121
|
|
|
105
122
|
const factoryCall = parent.callee;
|
|
123
|
+
|
|
106
124
|
return (
|
|
107
125
|
factoryCall.type === "CallExpression" &&
|
|
108
126
|
isEffectMethod(sourceCode, factoryCall.callee, "fn")
|
|
@@ -127,12 +145,14 @@ export const noTryCatchInEffectGeneratorsRule = defineRule({
|
|
|
127
145
|
TryStatement(node) {
|
|
128
146
|
if (!node.handler) return;
|
|
129
147
|
const owner = nearestEnclosingFunction(node);
|
|
148
|
+
|
|
130
149
|
if (
|
|
131
150
|
!owner?.generator ||
|
|
132
151
|
!isRecognisedEffectGenerator(context.sourceCode, owner)
|
|
133
152
|
) {
|
|
134
153
|
return;
|
|
135
154
|
}
|
|
155
|
+
|
|
136
156
|
context.report({ node, messageId: "useEffectErrorChannel" });
|
|
137
157
|
},
|
|
138
158
|
};
|
|
@@ -3,10 +3,12 @@ import { defineRule } from "@oxlint/plugins";
|
|
|
3
3
|
import type { ESTree } from "@oxlint/plugins";
|
|
4
4
|
|
|
5
5
|
type FunctionNode = ESTree.Function | ESTree.ArrowFunctionExpression;
|
|
6
|
+
|
|
6
7
|
type TypeAssertion = ESTree.TSAsExpression | ESTree.TSTypeAssertion;
|
|
7
8
|
|
|
8
9
|
function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
|
|
9
10
|
let current: ESTree.Node | null = node.parent;
|
|
11
|
+
|
|
10
12
|
while (current) {
|
|
11
13
|
if (
|
|
12
14
|
current.type === "FunctionDeclaration" ||
|
|
@@ -15,8 +17,10 @@ function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
|
|
|
15
17
|
) {
|
|
16
18
|
return current;
|
|
17
19
|
}
|
|
20
|
+
|
|
18
21
|
current = current.parent;
|
|
19
22
|
}
|
|
23
|
+
|
|
20
24
|
return null;
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -25,6 +29,7 @@ function assertedEventParameter(node: TypeAssertion): {
|
|
|
25
29
|
readonly property: "currentTarget" | "target";
|
|
26
30
|
} | null {
|
|
27
31
|
const expression = node.expression;
|
|
32
|
+
|
|
28
33
|
if (
|
|
29
34
|
expression.type !== "MemberExpression" ||
|
|
30
35
|
expression.computed ||
|
|
@@ -35,9 +40,11 @@ function assertedEventParameter(node: TypeAssertion): {
|
|
|
35
40
|
) {
|
|
36
41
|
return null;
|
|
37
42
|
}
|
|
43
|
+
|
|
38
44
|
const parameterName = expression.object.name;
|
|
39
45
|
const property = expression.property.name;
|
|
40
46
|
const owner = nearestEnclosingFunction(node);
|
|
47
|
+
|
|
41
48
|
if (
|
|
42
49
|
!owner?.params.some(
|
|
43
50
|
(parameter) =>
|
|
@@ -46,6 +53,7 @@ function assertedEventParameter(node: TypeAssertion): {
|
|
|
46
53
|
) {
|
|
47
54
|
return null;
|
|
48
55
|
}
|
|
56
|
+
|
|
49
57
|
return {
|
|
50
58
|
parameter: parameterName,
|
|
51
59
|
property,
|
|
@@ -68,6 +76,7 @@ export const preferEventParameterTypeRule = defineRule({
|
|
|
68
76
|
create(context) {
|
|
69
77
|
const checkAssertion = (node: TypeAssertion) => {
|
|
70
78
|
const eventParameter = assertedEventParameter(node);
|
|
79
|
+
|
|
71
80
|
if (!eventParameter) return;
|
|
72
81
|
context.report({
|
|
73
82
|
node,
|
|
@@ -75,6 +84,7 @@ export const preferEventParameterTypeRule = defineRule({
|
|
|
75
84
|
data: eventParameter,
|
|
76
85
|
});
|
|
77
86
|
};
|
|
87
|
+
|
|
78
88
|
return {
|
|
79
89
|
TSAsExpression: checkAssertion,
|
|
80
90
|
TSTypeAssertion: checkAssertion,
|