@timmo001/oxlint-rules 0.3.4 → 0.3.6
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 +53 -4
- package/package.json +11 -9
- package/skills/install-timmo-oxlint-rules/SKILL.md +13 -3
- package/skills/install-timmo-oxlint-rules/scripts/copy.mjs +5 -0
- package/skills/release-oxlint-rules/SKILL.md +20 -8
- 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
|
@@ -5,12 +5,21 @@ Shared Oxlint plugins and configs built around the unchanged
|
|
|
5
5
|
locally owned generic rules under `timmo` and Effect rules under
|
|
6
6
|
`timmo-effect`.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
This package targets the latest stable Oxlint toolchain. Oxlint's
|
|
9
|
+
[JavaScript plugin API is alpha](https://oxc.rs/docs/guide/usage/linter/config.html)
|
|
10
|
+
and is outside its semver guarantees, so each rules release declares the exact
|
|
11
|
+
`oxlint` and `@oxlint/plugins` versions it has validated. Older toolchains and
|
|
12
|
+
untested future versions are not part of the support contract.
|
|
10
13
|
|
|
11
14
|
## Install
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
Check the published rules package's `peerDependencies` against the official npm
|
|
17
|
+
`latest` versions of `oxlint` and `@oxlint/plugins`. If they differ, a validated
|
|
18
|
+
rules release is needed before upgrading. Do not bypass peer checks or override
|
|
19
|
+
Vite Plus's bundled dependencies to make an unsupported version fit.
|
|
20
|
+
|
|
21
|
+
Once the versions match, install from npm with the package manager already used
|
|
22
|
+
by the repository:
|
|
14
23
|
|
|
15
24
|
```sh
|
|
16
25
|
bun add --dev --exact @timmo001/oxlint-rules oxlint @oxlint/plugins
|
|
@@ -42,6 +51,11 @@ Both configs can be installed from JSR with its npm compatibility support.
|
|
|
42
51
|
npmjs.org remains the default for package managers that resolve ordinary
|
|
43
52
|
package names from `node_modules`.
|
|
44
53
|
|
|
54
|
+
For type-aware linting, also pin the latest `oxlint-tsgolint` version that meets
|
|
55
|
+
Oxlint's declared peer requirement and has passed the package check. Enable
|
|
56
|
+
`options: { typeAware: true }` in the consumer's config. The shared configs do
|
|
57
|
+
not enable type-aware linting themselves.
|
|
58
|
+
|
|
45
59
|
## Copy rules
|
|
46
60
|
|
|
47
61
|
Copy a reviewed snapshot when a repository should own the rule source:
|
|
@@ -83,10 +97,45 @@ Initialise the upstream source and run the package checks:
|
|
|
83
97
|
git submodule update --init --recursive
|
|
84
98
|
mise run check
|
|
85
99
|
mise run build
|
|
100
|
+
mise run test:package
|
|
86
101
|
npm pack --dry-run
|
|
87
|
-
bunx jsr publish --dry-run
|
|
102
|
+
bunx jsr@0.14.3 publish --dry-run --allow-dirty
|
|
88
103
|
```
|
|
89
104
|
|
|
105
|
+
`bun run lint` builds the local plugins before linting all maintained code,
|
|
106
|
+
including tests, tooling, the root config and packaged skill scripts. The root
|
|
107
|
+
config extends the local `recommended-effect` source config, enabling every
|
|
108
|
+
registered rule in `anti-slop`, `timmo`, `anti-slop-effect` and `timmo-effect`.
|
|
109
|
+
Plugin imports resolve through this package's exports to the fresh `dist` build.
|
|
110
|
+
|
|
111
|
+
Lint disables nested configs so the vendored submodule's config cannot override
|
|
112
|
+
the root exclusions. Generated output, vendored source and agent directories
|
|
113
|
+
remain excluded. RuleTester fixture strings retain their intentional violations;
|
|
114
|
+
the surrounding test code is linted with the full rule set.
|
|
115
|
+
|
|
116
|
+
`mise run test:package` builds and packs once, then installs the tarball in an
|
|
117
|
+
isolated consumer using the exact toolchain pins from `package.json` and strict
|
|
118
|
+
peer checks. It verifies both recommended configs, diagnostics from all four
|
|
119
|
+
shared namespaces, and valid and invalid type-aware examples. CI runs this
|
|
120
|
+
through the existing package workflow's build step. It also requires matching,
|
|
121
|
+
exact Oxlint development and peer versions.
|
|
122
|
+
|
|
123
|
+
### Toolchain updates and releases
|
|
124
|
+
|
|
125
|
+
Renovate's shared preset groups Oxlint, `@oxlint/plugins` and `oxlint-tsgolint`.
|
|
126
|
+
The local rule keeps both development and peer dependencies pinned instead of
|
|
127
|
+
widening peer ranges. For each update:
|
|
128
|
+
|
|
129
|
+
1. Verify the official npm `latest` versions and Oxlint's tsgolint peer
|
|
130
|
+
requirement. Keep Oxlint and plugins on the same latest version and regenerate
|
|
131
|
+
the lockfile with the selected tsgolint version.
|
|
132
|
+
2. Run the checks and package dry-runs above. Fix any API incompatibility before
|
|
133
|
+
accepting the new pins; do not disable rules or bypass peer failures.
|
|
134
|
+
3. Review and merge the validated update. Consumers need a separately approved
|
|
135
|
+
version bump and release to receive the new peer contract. Publishing a
|
|
136
|
+
GitHub release triggers the existing npm and JSR workflows; both must succeed
|
|
137
|
+
before the update is available to consumers.
|
|
138
|
+
|
|
90
139
|
Advance `vendor/anti-slop` only by updating its Git submodule commit. Keep its
|
|
91
140
|
source and MIT licence unchanged, then run the full package checks so both
|
|
92
141
|
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.6",
|
|
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,24 @@
|
|
|
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
|
+
"test:package": "node --experimental-strip-types scripts/test-package.ts",
|
|
45
46
|
"typecheck": "tsc --noEmit"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"@oxlint/plugins": "1.
|
|
49
|
-
"oxlint": "1.
|
|
49
|
+
"@oxlint/plugins": "1.85.0",
|
|
50
|
+
"oxlint": "1.85.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@oxlint/plugins": "1.
|
|
53
|
+
"@oxlint/plugins": "1.85.0",
|
|
53
54
|
"@types/bun": "^1.3.14",
|
|
54
|
-
"oxlint": "1.
|
|
55
|
+
"oxlint": "1.85.0",
|
|
56
|
+
"oxlint-tsgolint": "7.0.2002",
|
|
55
57
|
"prettier": "^3.9.5",
|
|
56
58
|
"skills-ref": "0.1.5",
|
|
57
59
|
"typescript": "^7.0.0"
|
|
@@ -14,8 +14,12 @@ description: >-
|
|
|
14
14
|
instructions, and normal checks. Use the current working directory unless
|
|
15
15
|
the user names another target.
|
|
16
16
|
3. Preserve existing ignores, overrides, plugins, and repository-owned rules.
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Verify the official npm `latest` versions of `oxlint` and `@oxlint/plugins`.
|
|
18
|
+
They must match each other and the exact peers of the selected rules release.
|
|
19
|
+
Support is latest-only: if the published peer contract has not caught up,
|
|
20
|
+
report the pending rules release instead of widening peers or bypassing
|
|
21
|
+
package-manager failures. Do not force upgrades of Vite Plus's transitive
|
|
22
|
+
dependencies or assume its bundled plugins satisfy this contract.
|
|
19
23
|
|
|
20
24
|
## Package
|
|
21
25
|
|
|
@@ -28,6 +32,10 @@ description: >-
|
|
|
28
32
|
instead only when `effect` is a direct dependency or the user explicitly
|
|
29
33
|
requests it.
|
|
30
34
|
4. Keep dependency and config edits visible. Do not delegate them to a script.
|
|
35
|
+
5. For type-aware linting, explicitly pin the latest `oxlint-tsgolint` that
|
|
36
|
+
satisfies Oxlint's peer requirement and was validated with the rules release.
|
|
37
|
+
Enable `options.typeAware` in the target config and verify type-aware linting
|
|
38
|
+
alongside the shared rules.
|
|
31
39
|
|
|
32
40
|
## Copy rules
|
|
33
41
|
|
|
@@ -43,6 +51,8 @@ description: >-
|
|
|
43
51
|
output as authoritative rather than maintaining plugin or rule inventories
|
|
44
52
|
in this skill.
|
|
45
53
|
|
|
46
|
-
Run the target repository's
|
|
54
|
+
Run the target repository's lint command to verify the configuration loads, plus
|
|
55
|
+
any checks explicitly required by that repository. Run typechecks, tests, or a
|
|
56
|
+
build only when code or dependencies changed in a way those checks cover. Report
|
|
47
57
|
package-manager changes, preserved local configuration, enabled rule groups,
|
|
48
58
|
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);
|
|
@@ -17,22 +17,34 @@ description: >-
|
|
|
17
17
|
its release exists. Resolve the next version from the requested major,
|
|
18
18
|
minor, patch, or exact version. Ask only when the release level is missing.
|
|
19
19
|
Do not add a `v` prefix when the repository's existing tags omit it.
|
|
20
|
-
3.
|
|
21
|
-
`
|
|
20
|
+
3. Verify official npm `latest` metadata for `oxlint`, `@oxlint/plugins` and
|
|
21
|
+
`oxlint-tsgolint`. Support is latest-only: keep Oxlint and plugins on the same
|
|
22
|
+
latest version, pin their development and peer dependencies exactly, and
|
|
23
|
+
select the latest tsgolint satisfying Oxlint's peer requirement. Regenerate
|
|
24
|
+
`bun.lock`. The shared Renovate preset groups this toolchain; the local rule
|
|
25
|
+
keeps peers pinned. Resolve compatibility failures before releasing, without
|
|
26
|
+
widening peers, disabling rules or forcing transitive dependency upgrades.
|
|
27
|
+
4. Update the version in `package.json` and `jsr.json` only when a version bump
|
|
28
|
+
is requested. Before committing, run `mise run check`, `mise run build`,
|
|
29
|
+
`mise run test:package`, `npm pack --dry-run`, and
|
|
22
30
|
`bunx jsr@0.14.3 publish --dry-run --allow-dirty`. The dirty-tree flag is
|
|
23
|
-
required because the intended
|
|
24
|
-
|
|
31
|
+
required because the intended changes are not committed yet. The package
|
|
32
|
+
check installs packed output with strict peers and verifies both recommended
|
|
33
|
+
configs, actual shared-rule diagnostics and type-aware linting.
|
|
34
|
+
5. Treat a direct request to create or publish the release as authorisation for
|
|
25
35
|
its version commit, push, and GitHub release. Follow the active environment's
|
|
26
36
|
guarded commit and push workflow, committing only the release metadata with
|
|
27
37
|
`Release Oxlint rules <version>`. A request to prepare or plan a release does
|
|
28
38
|
not authorise publication.
|
|
29
|
-
|
|
39
|
+
6. Resolve the target only after the push with `git rev-parse HEAD`. Pass the
|
|
30
40
|
resulting full 40-character SHA as the `--target` value when running
|
|
31
41
|
`gh release create`. Never pass an abbreviated SHA as `target_commitish`.
|
|
32
|
-
|
|
42
|
+
7. Create the release before resolving workflow targets because publication is
|
|
33
43
|
triggered by the `release.published` event. Watch only the release commit's
|
|
34
44
|
exact workflow runs and require both `Publish to npm` and `Publish to JSR` to
|
|
35
45
|
succeed.
|
|
36
|
-
|
|
46
|
+
8. Report the release URL, version commit, validation results, and npm and JSR
|
|
37
47
|
publication conclusions. Do not report the release as complete while either
|
|
38
|
-
publication job is pending or failed.
|
|
48
|
+
publication job is pending or failed. A merged toolchain update alone does
|
|
49
|
+
not deliver its peer contract to consumers; preparation ends with publication
|
|
50
|
+
pending until a version bump and release are approved.
|
|
@@ -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,
|