@zgeoff/oxlint-config 0.0.2 → 0.0.4
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 +9 -4
- package/oxlintrc.json +5 -1
- package/package.json +8 -1
- package/plugin.js +93 -2
package/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Shareable [oxlint](https://oxc.rs/docs/guide/usage/linter.html) config: every category at `error`,
|
|
4
4
|
restriction-rule cherry-picks, comment-style enforcement via `@stylistic`, and a bundled JS plugin
|
|
5
|
-
(`zgeoff/*` rules) banning
|
|
5
|
+
(`zgeoff/*` rules) banning shapes no native oxlint rule can express — top-level arrows, nested
|
|
6
6
|
function declarations, bare named exports, destructured and inline-typed parameters, ternary and
|
|
7
|
-
await arguments
|
|
7
|
+
await arguments, awaits hidden inside a control-flow condition or a `&&`/`||`/`??` chain, and
|
|
8
|
+
single-line `/** … */` blocks (auto-fixed to multi-line; inline `@type`/`@lends` casts exempt).
|
|
8
9
|
|
|
9
10
|
## Usage
|
|
10
11
|
|
|
11
12
|
```sh
|
|
12
|
-
bun add -d @zgeoff/oxlint-config oxlint
|
|
13
|
+
bun add -d @zgeoff/oxlint-config oxlint @stylistic/eslint-plugin
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
Extend it from `.oxlintrc.json` — `extends` resolves file paths only, so point at the file inside
|
|
@@ -36,6 +37,10 @@ The plugin is addressable on its own at `@zgeoff/oxlint-config/plugin` for confi
|
|
|
36
37
|
## Notes
|
|
37
38
|
|
|
38
39
|
- The config enables the `typescript`, `unicorn`, `oxc`, `import`, and `promise` plugins and loads
|
|
39
|
-
`@stylistic/eslint-plugin`
|
|
40
|
+
`@stylistic/eslint-plugin` through `jsPlugins`. oxlint resolves `jsPlugins` specifiers from the
|
|
41
|
+
project root, not from the config file that names them — so `@stylistic/eslint-plugin` must be a
|
|
42
|
+
direct dependency of the consuming project. Hoisting package managers (bun's default linker, npm)
|
|
43
|
+
resolve it transitively anyway, but isolated layouts (pnpm, `bun install --linker isolated`) fail
|
|
44
|
+
with `Cannot find module '@stylistic/eslint-plugin'` unless it's installed directly.
|
|
40
45
|
- Type-aware rules activate under `oxlint --type-aware` (requires `oxlint-tsgolint`); the config
|
|
41
46
|
works without it.
|
package/oxlintrc.json
CHANGED
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"zgeoff/no-pick-destructuring": "error",
|
|
24
24
|
"zgeoff/no-ternary-args": "error",
|
|
25
25
|
"zgeoff/no-await-args": "error",
|
|
26
|
+
"zgeoff/no-await-in-condition": "error",
|
|
27
|
+
"zgeoff/no-await-in-logical": "error",
|
|
28
|
+
"zgeoff/no-single-line-jsdoc": "error",
|
|
26
29
|
|
|
27
30
|
// enforced comment style — line comments unless JSDoc or exclamation
|
|
28
31
|
"@stylistic/multiline-comment-style": [
|
|
@@ -140,8 +143,9 @@
|
|
|
140
143
|
{
|
|
141
144
|
"files": ["**/*.js", "**/*.cjs", "**/*.mjs"],
|
|
142
145
|
// untyped JS is `any` end to end, so the unsafe-* rules flag every
|
|
143
|
-
// expression
|
|
146
|
+
// expression and strict-boolean-expressions every conditional
|
|
144
147
|
"rules": {
|
|
148
|
+
"typescript/strict-boolean-expressions": "off",
|
|
145
149
|
"typescript/no-unsafe-argument": "off",
|
|
146
150
|
"typescript/no-unsafe-assignment": "off",
|
|
147
151
|
"typescript/no-unsafe-call": "off",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zgeoff/oxlint-config",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Shareable oxlint config: strict categories, house-style rules, and a custom JS plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,9 +22,16 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"typecheck": "tsc"
|
|
27
|
+
},
|
|
25
28
|
"dependencies": {
|
|
26
29
|
"@stylistic/eslint-plugin": "5.10.0"
|
|
27
30
|
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/bun": "1.3.14",
|
|
33
|
+
"typescript": "7.0.1-rc"
|
|
34
|
+
},
|
|
28
35
|
"peerDependencies": {
|
|
29
36
|
"oxlint": ">=1.71.0"
|
|
30
37
|
}
|
package/plugin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Custom lint rules, loaded through oxlint's jsPlugins (ESLint v9 rule API).
|
|
2
|
-
// Each rule bans
|
|
3
|
-
//
|
|
2
|
+
// Each rule bans a shape no native oxlint rule can express — esquery selectors
|
|
3
|
+
// for AST shapes, a source-comment scan where the target isn't
|
|
4
|
+
// esquery-selectable.
|
|
4
5
|
|
|
5
6
|
function banSelectors(type, message, selectors) {
|
|
6
7
|
return {
|
|
@@ -16,6 +17,82 @@ function banSelectors(type, message, selectors) {
|
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
// mirrors eslint-plugin-jsdoc's default singleLineTags: blocks that exist to
|
|
21
|
+
// cast inline (`/** @type {Foo} */ (bar)`) stay single-line by design
|
|
22
|
+
const inlineTagPattern = /^@(?:type|lends)\b/u;
|
|
23
|
+
|
|
24
|
+
function isSingleLineJSDoc(comment) {
|
|
25
|
+
return (
|
|
26
|
+
comment.type === 'Block' &&
|
|
27
|
+
comment.value.startsWith('*') &&
|
|
28
|
+
comment.loc.start.line === comment.loc.end.line
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Builds the multi-line replacement for a single-line JSDoc comment, or null
|
|
34
|
+
* when the comment shares its line with code — expanding those in place would
|
|
35
|
+
* scramble the surrounding statement, so they are reported without a fix.
|
|
36
|
+
*/
|
|
37
|
+
function planExpansion(text, comment) {
|
|
38
|
+
if (!isOwnLine(text, comment)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lineStart = text.lastIndexOf('\n', comment.range[0] - 1) + 1;
|
|
43
|
+
const indent = text.slice(lineStart, comment.range[0]);
|
|
44
|
+
const body = comment.value.slice(1).trim();
|
|
45
|
+
const bodyLine = body === '' ? `${indent} *` : `${indent} * ${body}`;
|
|
46
|
+
|
|
47
|
+
return `/**\n${bodyLine}\n${indent} */`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isOwnLine(text, comment) {
|
|
51
|
+
const [start, end] = comment.range;
|
|
52
|
+
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
53
|
+
const lineEnd = text.indexOf('\n', end);
|
|
54
|
+
const sliceEnd = lineEnd === -1 ? text.length : lineEnd;
|
|
55
|
+
|
|
56
|
+
return /^[ \t]*$/u.test(text.slice(lineStart, start)) && text.slice(end, sliceEnd).trim() === '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const noSingleLineJSDoc = {
|
|
60
|
+
meta: {
|
|
61
|
+
type: 'layout',
|
|
62
|
+
fixable: 'whitespace',
|
|
63
|
+
messages: {
|
|
64
|
+
singleLine:
|
|
65
|
+
'Write JSDoc blocks multi-line: `/**` alone, one `*`-prefixed line per point, `*/` alone.',
|
|
66
|
+
},
|
|
67
|
+
schema: [],
|
|
68
|
+
},
|
|
69
|
+
create(context) {
|
|
70
|
+
return {
|
|
71
|
+
Program() {
|
|
72
|
+
const offenders = context.sourceCode
|
|
73
|
+
.getAllComments()
|
|
74
|
+
.filter(
|
|
75
|
+
(comment) =>
|
|
76
|
+
isSingleLineJSDoc(comment) && !inlineTagPattern.test(comment.value.slice(1).trim()),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
for (const comment of offenders) {
|
|
80
|
+
const replacement = planExpansion(context.sourceCode.text, comment);
|
|
81
|
+
|
|
82
|
+
context.report({
|
|
83
|
+
loc: comment.loc,
|
|
84
|
+
messageId: 'singleLine',
|
|
85
|
+
fix:
|
|
86
|
+
replacement === null
|
|
87
|
+
? undefined
|
|
88
|
+
: (fixer) => fixer.replaceTextRange(comment.range, replacement),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
19
96
|
const plugin = {
|
|
20
97
|
meta: { name: 'zgeoff' },
|
|
21
98
|
rules: {
|
|
@@ -75,6 +152,20 @@ const plugin = {
|
|
|
75
152
|
'Await into a named const instead of awaiting inside an argument list.',
|
|
76
153
|
['CallExpression > AwaitExpression.arguments', 'NewExpression > AwaitExpression.arguments'],
|
|
77
154
|
),
|
|
155
|
+
'no-await-in-condition': banSelectors(
|
|
156
|
+
'suggestion',
|
|
157
|
+
'Await into a named const before the statement instead of inside a control-flow condition.',
|
|
158
|
+
[
|
|
159
|
+
':matches(IfStatement, WhileStatement, DoWhileStatement, ForStatement, ConditionalExpression) > AwaitExpression.test',
|
|
160
|
+
'SwitchStatement > AwaitExpression.discriminant',
|
|
161
|
+
],
|
|
162
|
+
),
|
|
163
|
+
'no-await-in-logical': banSelectors(
|
|
164
|
+
'suggestion',
|
|
165
|
+
'Await into a named const instead of chaining it after a && / || / ?? operator.',
|
|
166
|
+
['LogicalExpression > AwaitExpression'],
|
|
167
|
+
),
|
|
168
|
+
'no-single-line-jsdoc': noSingleLineJSDoc,
|
|
78
169
|
},
|
|
79
170
|
};
|
|
80
171
|
|