@zgeoff/oxlint-config 0.0.5 → 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.
- package/README.md +20 -3
- package/function-verb.js +2 -10
- package/max-consecutive-line-comments.js +76 -0
- package/no-jsdoc.js +40 -0
- package/oxlintrc.json +2 -1
- package/package.json +3 -1
- package/plugin.js +7 -81
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ Shareable [oxlint](https://oxc.rs/docs/guide/usage/linter.html) config: every ca
|
|
|
4
4
|
restriction-rule cherry-picks, comment-style enforcement via `@stylistic`, and a bundled JS plugin
|
|
5
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, awaits hidden inside a control-flow condition or a `&&`/`||`/`??` chain,
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
await arguments, awaits hidden inside a control-flow condition or a `&&`/`||`/`??` chain, every
|
|
8
|
+
JSDoc block, and any run of more than three line comments. `zgeoff/function-verb` enforces the
|
|
9
|
+
function-naming taxonomy from the shared agent guidelines.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
@@ -54,6 +54,23 @@ Options extend the shipped set per repo:
|
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
## `zgeoff/no-jsdoc` and `zgeoff/max-consecutive-line-comments`
|
|
58
|
+
|
|
59
|
+
A comment holds only what neither the code, a type, a test name, nor the subsystem doc can hold: the
|
|
60
|
+
reason a line does the non-obvious thing. `no-jsdoc` reports every `/** … */` block (inline
|
|
61
|
+
`@type`/`@lends` casts exempt). `max-consecutive-line-comments` reports a run of own-line `//`
|
|
62
|
+
comments longer than `max` (default 3); tool directives (`oxlint-`, `eslint-`, `@ts-`, …) neither
|
|
63
|
+
count nor join the prose around them. Neither rule fixes: a deleted comment is a decision, not a
|
|
64
|
+
whitespace change.
|
|
65
|
+
|
|
66
|
+
```jsonc
|
|
67
|
+
{
|
|
68
|
+
"rules": {
|
|
69
|
+
"zgeoff/max-consecutive-line-comments": ["error", { "max": 3 }],
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
57
74
|
## Notes
|
|
58
75
|
|
|
59
76
|
- The config enables the `typescript`, `unicorn`, `oxc`, `import`, and `promise` plugins and loads
|
package/function-verb.js
CHANGED
|
@@ -105,7 +105,8 @@ function isVerbMatch(name, verb, bareAllowed) {
|
|
|
105
105
|
return bareAllowed;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
// a digit also ends the verb: acronyms like 2FA start camelCase segments
|
|
109
|
+
return name.startsWith(verb) && /[A-Z0-9]/u.test(name.charAt(verb.length));
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
function findAllowedVerb(name, verbs) {
|
|
@@ -147,15 +148,6 @@ function planReport(name, verbs, exemptNames) {
|
|
|
147
148
|
};
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
/**
|
|
151
|
-
* Enforces the function-naming taxonomy on function declarations,
|
|
152
|
-
* function-valued variables, and class methods. Object-literal properties are
|
|
153
|
-
* exempt — they overwhelmingly implement externally-defined shapes (rule
|
|
154
|
-
* visitors, route tables) whose names the author doesn't choose — as are
|
|
155
|
-
* names not starting with a lowercase letter (components, classes).
|
|
156
|
-
* Options: `verbs` appends repo-local verbs to the shipped taxonomy;
|
|
157
|
-
* `exemptNames` skips exact names.
|
|
158
|
-
*/
|
|
159
151
|
const functionVerb = {
|
|
160
152
|
meta: {
|
|
161
153
|
type: 'suggestion',
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// tool directives are not prose: they neither count toward a run nor join
|
|
2
|
+
// the prose on either side of them into one
|
|
3
|
+
const directivePattern =
|
|
4
|
+
/^\s*(?:oxlint-|eslint-|@ts-|prettier-|oxfmt-|biome-ignore|#region|#endregion|\/\s*<)/u;
|
|
5
|
+
|
|
6
|
+
function isOwnLine(text, comment) {
|
|
7
|
+
const lineStart = text.lastIndexOf('\n', comment.range[0] - 1) + 1;
|
|
8
|
+
|
|
9
|
+
return /^[ \t]*$/u.test(text.slice(lineStart, comment.range[0]));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isProseLine(text, comment) {
|
|
13
|
+
return (
|
|
14
|
+
comment.type === 'Line' && !directivePattern.test(comment.value) && isOwnLine(text, comment)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// a run is prose line comments on consecutive lines; whatever else occupies
|
|
19
|
+
// a line between two of them (code, a blank, a directive, a block comment)
|
|
20
|
+
// breaks adjacency and so ends the run
|
|
21
|
+
function collectRuns(text, comments) {
|
|
22
|
+
const runs = [];
|
|
23
|
+
|
|
24
|
+
for (const comment of comments.filter((candidate) => isProseLine(text, candidate))) {
|
|
25
|
+
const run = runs.at(-1);
|
|
26
|
+
|
|
27
|
+
if (run !== undefined && comment.loc.start.line === run.at(-1).loc.end.line + 1) {
|
|
28
|
+
run.push(comment);
|
|
29
|
+
} else {
|
|
30
|
+
runs.push([comment]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return runs;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const maxConsecutiveLineComments = {
|
|
38
|
+
meta: {
|
|
39
|
+
type: 'suggestion',
|
|
40
|
+
messages: {
|
|
41
|
+
tooLong:
|
|
42
|
+
'This comment run is {{count}} lines; the limit is {{max}}. Cut it to the fact the code cannot show, or move it into a test name or the subsystem doc.',
|
|
43
|
+
},
|
|
44
|
+
schema: [
|
|
45
|
+
{
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
max: { type: 'integer', minimum: 1 },
|
|
49
|
+
},
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
create(context) {
|
|
55
|
+
const max = context.options[0]?.max ?? 3;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
Program() {
|
|
59
|
+
const text = context.sourceCode.text;
|
|
60
|
+
const runs = collectRuns(text, context.sourceCode.getAllComments());
|
|
61
|
+
|
|
62
|
+
for (const run of runs) {
|
|
63
|
+
if (run.length > max) {
|
|
64
|
+
context.report({
|
|
65
|
+
loc: { start: run[0].loc.start, end: run.at(-1).loc.end },
|
|
66
|
+
messageId: 'tooLong',
|
|
67
|
+
data: { count: String(run.length), max: String(max) },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default maxConsecutiveLineComments;
|
package/no-jsdoc.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// mirrors eslint-plugin-jsdoc's default singleLineTags: a block that exists to
|
|
2
|
+
// cast inline (`/** @type {Foo} */ (bar)`) is a type annotation, not prose
|
|
3
|
+
const inlineTagPattern = /^@(?:type|lends)\b/u;
|
|
4
|
+
|
|
5
|
+
function isJSDoc(comment) {
|
|
6
|
+
return comment.type === 'Block' && comment.value.startsWith('*');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isInlineCast(comment) {
|
|
10
|
+
return (
|
|
11
|
+
comment.loc.start.line === comment.loc.end.line &&
|
|
12
|
+
inlineTagPattern.test(comment.value.slice(1).trim())
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const noJSDoc = {
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
messages: {
|
|
20
|
+
jsdoc:
|
|
21
|
+
'Delete this JSDoc block. A rule a caller must respect is a test whose name states it or a sentence in the subsystem doc; the reason a line does the non-obvious thing is a `//` at that line.',
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
Program() {
|
|
28
|
+
const offenders = context.sourceCode
|
|
29
|
+
.getAllComments()
|
|
30
|
+
.filter((comment) => isJSDoc(comment) && !isInlineCast(comment));
|
|
31
|
+
|
|
32
|
+
for (const comment of offenders) {
|
|
33
|
+
context.report({ loc: comment.loc, messageId: 'jsdoc' });
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default noJSDoc;
|
package/oxlintrc.json
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"zgeoff/no-await-args": "error",
|
|
26
26
|
"zgeoff/no-await-in-condition": "error",
|
|
27
27
|
"zgeoff/no-await-in-logical": "error",
|
|
28
|
-
"zgeoff/no-
|
|
28
|
+
"zgeoff/no-jsdoc": "error",
|
|
29
|
+
"zgeoff/max-consecutive-line-comments": ["error", { "max": 3 }],
|
|
29
30
|
"zgeoff/function-verb": "error",
|
|
30
31
|
|
|
31
32
|
// enforced comment style — line comments unless JSDoc or exclamation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zgeoff/oxlint-config",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Shareable oxlint config: strict categories, house-style rules, and a custom JS plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"oxlintrc.json",
|
|
13
13
|
"plugin.js",
|
|
14
14
|
"function-verb.js",
|
|
15
|
+
"max-consecutive-line-comments.js",
|
|
16
|
+
"no-jsdoc.js",
|
|
15
17
|
"index.js"
|
|
16
18
|
],
|
|
17
19
|
"type": "module",
|
package/plugin.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import functionVerb from './function-verb.js';
|
|
2
|
+
import maxConsecutiveLineComments from './max-consecutive-line-comments.js';
|
|
3
|
+
import noJSDoc from './no-jsdoc.js';
|
|
2
4
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// esquery-selectable.
|
|
5
|
+
// custom rules loaded through oxlint's jsPlugins (ESLint v9 rule API): each
|
|
6
|
+
// bans a shape no native oxlint rule can express, as an esquery selector or a
|
|
7
|
+
// source-comment scan
|
|
7
8
|
|
|
8
9
|
function buildBanRule(type, message, selectors) {
|
|
9
10
|
return {
|
|
@@ -19,82 +20,6 @@ function buildBanRule(type, message, selectors) {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// mirrors eslint-plugin-jsdoc's default singleLineTags: blocks that exist to
|
|
23
|
-
// cast inline (`/** @type {Foo} */ (bar)`) stay single-line by design
|
|
24
|
-
const inlineTagPattern = /^@(?:type|lends)\b/u;
|
|
25
|
-
|
|
26
|
-
function isSingleLineJSDoc(comment) {
|
|
27
|
-
return (
|
|
28
|
-
comment.type === 'Block' &&
|
|
29
|
-
comment.value.startsWith('*') &&
|
|
30
|
-
comment.loc.start.line === comment.loc.end.line
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Builds the multi-line replacement for a single-line JSDoc comment, or null
|
|
36
|
-
* when the comment shares its line with code — expanding those in place would
|
|
37
|
-
* scramble the surrounding statement, so they are reported without a fix.
|
|
38
|
-
*/
|
|
39
|
-
function planExpansion(text, comment) {
|
|
40
|
-
if (!isOwnLine(text, comment)) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const lineStart = text.lastIndexOf('\n', comment.range[0] - 1) + 1;
|
|
45
|
-
const indent = text.slice(lineStart, comment.range[0]);
|
|
46
|
-
const body = comment.value.slice(1).trim();
|
|
47
|
-
const bodyLine = body === '' ? `${indent} *` : `${indent} * ${body}`;
|
|
48
|
-
|
|
49
|
-
return `/**\n${bodyLine}\n${indent} */`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function isOwnLine(text, comment) {
|
|
53
|
-
const [start, end] = comment.range;
|
|
54
|
-
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
55
|
-
const lineEnd = text.indexOf('\n', end);
|
|
56
|
-
const sliceEnd = lineEnd === -1 ? text.length : lineEnd;
|
|
57
|
-
|
|
58
|
-
return /^[ \t]*$/u.test(text.slice(lineStart, start)) && text.slice(end, sliceEnd).trim() === '';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const noSingleLineJSDoc = {
|
|
62
|
-
meta: {
|
|
63
|
-
type: 'layout',
|
|
64
|
-
fixable: 'whitespace',
|
|
65
|
-
messages: {
|
|
66
|
-
singleLine:
|
|
67
|
-
'Write JSDoc blocks multi-line: `/**` alone, one `*`-prefixed line per point, `*/` alone.',
|
|
68
|
-
},
|
|
69
|
-
schema: [],
|
|
70
|
-
},
|
|
71
|
-
create(context) {
|
|
72
|
-
return {
|
|
73
|
-
Program() {
|
|
74
|
-
const offenders = context.sourceCode
|
|
75
|
-
.getAllComments()
|
|
76
|
-
.filter(
|
|
77
|
-
(comment) =>
|
|
78
|
-
isSingleLineJSDoc(comment) && !inlineTagPattern.test(comment.value.slice(1).trim()),
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
for (const comment of offenders) {
|
|
82
|
-
const replacement = planExpansion(context.sourceCode.text, comment);
|
|
83
|
-
|
|
84
|
-
context.report({
|
|
85
|
-
loc: comment.loc,
|
|
86
|
-
messageId: 'singleLine',
|
|
87
|
-
fix:
|
|
88
|
-
replacement === null
|
|
89
|
-
? undefined
|
|
90
|
-
: (fixer) => fixer.replaceTextRange(comment.range, replacement),
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
|
|
98
23
|
const plugin = {
|
|
99
24
|
meta: { name: 'zgeoff' },
|
|
100
25
|
rules: {
|
|
@@ -167,7 +92,8 @@ const plugin = {
|
|
|
167
92
|
'Await into a named const instead of chaining it after a && / || / ?? operator.',
|
|
168
93
|
['LogicalExpression > AwaitExpression'],
|
|
169
94
|
),
|
|
170
|
-
'no-
|
|
95
|
+
'no-jsdoc': noJSDoc,
|
|
96
|
+
'max-consecutive-line-comments': maxConsecutiveLineComments,
|
|
171
97
|
'function-verb': functionVerb,
|
|
172
98
|
},
|
|
173
99
|
};
|