@zgeoff/oxlint-config 0.2.6 → 0.2.8
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 +31 -1
- package/no-file-snapshot.js +58 -0
- package/no-restricted-words.js +61 -0
- package/oxlintrc.json +1 -0
- package/package.json +5 -3
- package/plugin.js +4 -0
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ await arguments, awaits hidden inside a control-flow condition or a `&&`/`||`/`?
|
|
|
8
8
|
`switch` statement, every JSDoc block, any run of more than three line comments, and a repo config
|
|
9
9
|
that loosens either comment rule. `zgeoff/function-verb` enforces the function-naming taxonomy from
|
|
10
10
|
the shared agent guidelines, `zgeoff/test-beside-module` keeps each test file beside the module it
|
|
11
|
-
tests,
|
|
11
|
+
tests, `zgeoff/import-zones` fails an import that crosses a module boundary the repo declares,
|
|
12
|
+
`zgeoff/no-restricted-words` keeps the words in its `words` option out of a path's text, and
|
|
13
|
+
`zgeoff/no-file-snapshot` bans file snapshots.
|
|
12
14
|
|
|
13
15
|
## Usage
|
|
14
16
|
|
|
@@ -85,6 +87,34 @@ first statement, so put the comment above it:
|
|
|
85
87
|
A second test file for a module (`<name>.host.test.ts`) needs its own `<name>.host.<ext>`, so the
|
|
86
88
|
rule reports it beside only `<name>.ts`.
|
|
87
89
|
|
|
90
|
+
## `zgeoff/no-restricted-words`
|
|
91
|
+
|
|
92
|
+
Off unless a repo configures it. The `words` option lists the words to keep out of the text of a
|
|
93
|
+
file: string literals, template text and comments. A word matches whole and without case, so `imp`
|
|
94
|
+
reports `'IMP daemon'` and `'imp-e2e'` but not `'impl'` or `'imp_name'`. Identifiers are code, not
|
|
95
|
+
text, and the rule never reads them. Scope the rule to a path with `overrides`, and turn it off for
|
|
96
|
+
the paths that may name the words:
|
|
97
|
+
|
|
98
|
+
```jsonc
|
|
99
|
+
{
|
|
100
|
+
"rules": {
|
|
101
|
+
"zgeoff/no-restricted-words": ["error", { "words": ["imp", "impd"] }],
|
|
102
|
+
},
|
|
103
|
+
"overrides": [
|
|
104
|
+
{ "files": ["packages/execution/**"], "rules": { "zgeoff/no-restricted-words": "off" } },
|
|
105
|
+
],
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## `zgeoff/no-file-snapshot`
|
|
110
|
+
|
|
111
|
+
Snapshots are inline only, so the golden value sits in the test that pins it. The rule reports every
|
|
112
|
+
`toMatchSnapshot` and `toThrowErrorMatchingSnapshot`, which write a `.snap` file;
|
|
113
|
+
`toMatchInlineSnapshot` and `toThrowErrorMatchingInlineSnapshot` stay allowed. It also reports a
|
|
114
|
+
linted file that has a `__snapshots__/<file name>.snap` beside it, at the first statement, so a
|
|
115
|
+
file-level disable comment covers it. oxlint never visits a `.snap` file, so a snapshot file whose
|
|
116
|
+
test file is gone is not reported.
|
|
117
|
+
|
|
88
118
|
## `zgeoff/import-zones`
|
|
89
119
|
|
|
90
120
|
Off unless a repo configures it. The options name zones (path globs, relative to the repo root) and
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// the matchers that write `__snapshots__/<test file>.snap`; their inline
|
|
5
|
+
// counterparts keep the golden value in the test itself
|
|
6
|
+
const fileMatchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'];
|
|
7
|
+
|
|
8
|
+
const noFileSnapshot = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
messages: {
|
|
12
|
+
matcher:
|
|
13
|
+
'Replace `{{matcher}}` with its inline form: snapshots are inline only, so the golden value sits in the test.',
|
|
14
|
+
file: 'Delete `__snapshots__/{{name}}` and pin its values inline: snapshots are inline only, so the golden value sits in the test.',
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const entries = fileMatchers
|
|
20
|
+
.flatMap((matcher) => [
|
|
21
|
+
`MemberExpression[computed=false] > Identifier.property[name='${matcher}']`,
|
|
22
|
+
`MemberExpression[computed=true] > Literal.property[value='${matcher}']`,
|
|
23
|
+
])
|
|
24
|
+
.map((selector) => [
|
|
25
|
+
selector,
|
|
26
|
+
(node) =>
|
|
27
|
+
context.report({ node, messageId: 'matcher', data: { matcher: getMatcherName(node) } }),
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...Object.fromEntries(entries),
|
|
32
|
+
Program() {
|
|
33
|
+
const filename = context.physicalFilename;
|
|
34
|
+
const name = `${basename(filename)}.snap`;
|
|
35
|
+
|
|
36
|
+
if (!isFile(join(dirname(filename), '__snapshots__', name))) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// anchor on the first statement, not offset 0, so a disable comment
|
|
41
|
+
// at the top of the file sits before the report and covers it
|
|
42
|
+
const start = context.sourceCode.ast.body[0]?.loc.start ?? { line: 1, column: 0 };
|
|
43
|
+
|
|
44
|
+
context.report({ loc: { start, end: start }, messageId: 'file', data: { name } });
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function getMatcherName(node) {
|
|
51
|
+
return node.type === 'Identifier' ? node.name : node.value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isFile(path) {
|
|
55
|
+
return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default noFileSnapshot;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const noRestrictedWords = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
messages: {
|
|
5
|
+
restricted:
|
|
6
|
+
'Remove `{{word}}` from this {{place}}: the repo config restricts the word in this path.',
|
|
7
|
+
},
|
|
8
|
+
schema: [
|
|
9
|
+
{
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
words: { type: 'array', items: { type: 'string', minLength: 1 }, uniqueItems: true },
|
|
13
|
+
},
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const words = context.options[0]?.words ?? [];
|
|
20
|
+
|
|
21
|
+
if (words.length === 0) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pattern = buildWordPattern(words);
|
|
26
|
+
|
|
27
|
+
const checkText = (node, text, place) => {
|
|
28
|
+
for (const match of text.matchAll(pattern)) {
|
|
29
|
+
context.report({ node, messageId: 'restricted', data: { word: match[0], place } });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
Literal(node) {
|
|
35
|
+
if (typeof node.value === 'string') {
|
|
36
|
+
checkText(node, node.value, 'string');
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
TemplateElement(node) {
|
|
40
|
+
checkText(node, node.value.cooked ?? node.value.raw, 'template');
|
|
41
|
+
},
|
|
42
|
+
Program() {
|
|
43
|
+
for (const comment of context.sourceCode.getAllComments()) {
|
|
44
|
+
checkText(comment, comment.value, 'comment');
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// a letter, digit or underscore on either side makes the match part of a
|
|
52
|
+
// longer word (`impl`, `simple`), so only those characters bound it
|
|
53
|
+
function buildWordPattern(words) {
|
|
54
|
+
const alternatives = words.map((word) =>
|
|
55
|
+
word.replaceAll(/[$()*+.?[\\\]^{|}]/gu, String.raw`\$&`),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return new RegExp(`(?<![\\p{L}\\p{N}_])(?:${alternatives.join('|')})(?![\\p{L}\\p{N}_])`, 'giu');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default noRestrictedWords;
|
package/oxlintrc.json
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"zgeoff/no-comment-rule-override": "error",
|
|
32
32
|
"zgeoff/function-verb": "error",
|
|
33
33
|
"zgeoff/test-beside-module": "error",
|
|
34
|
+
"zgeoff/no-file-snapshot": "error",
|
|
34
35
|
|
|
35
36
|
// enforced comment style — line comments unless JSDoc or exclamation
|
|
36
37
|
"@stylistic/multiline-comment-style": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zgeoff/oxlint-config",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Shareable oxlint config: strict categories, house-style rules, and a custom JS plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
"oxlintrc.json",
|
|
13
13
|
"plugin.js",
|
|
14
14
|
"function-verb.js",
|
|
15
|
+
"no-restricted-words.js",
|
|
15
16
|
"max-consecutive-line-comments.js",
|
|
16
17
|
"no-jsdoc.js",
|
|
17
18
|
"no-comment-rule-override.js",
|
|
18
19
|
"test-beside-module.js",
|
|
20
|
+
"no-file-snapshot.js",
|
|
19
21
|
"import-zones.js",
|
|
20
22
|
"import-zones-schema.js",
|
|
21
23
|
"find-repo-root.js",
|
|
@@ -40,8 +42,8 @@
|
|
|
40
42
|
"@stylistic/eslint-plugin": "5.10.0"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
|
-
"@types/bun": "1.
|
|
44
|
-
"typescript": "7.0.
|
|
45
|
+
"@types/bun": "1.4.2",
|
|
46
|
+
"typescript": "7.0.2"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"oxlint": ">=1.78.0"
|
package/plugin.js
CHANGED
|
@@ -2,7 +2,9 @@ import functionVerb from './function-verb.js';
|
|
|
2
2
|
import importZones from './import-zones.js';
|
|
3
3
|
import maxConsecutiveLineComments from './max-consecutive-line-comments.js';
|
|
4
4
|
import noCommentRuleOverride from './no-comment-rule-override.js';
|
|
5
|
+
import noFileSnapshot from './no-file-snapshot.js';
|
|
5
6
|
import noJSDoc from './no-jsdoc.js';
|
|
7
|
+
import noRestrictedWords from './no-restricted-words.js';
|
|
6
8
|
import testBesideModule from './test-beside-module.js';
|
|
7
9
|
|
|
8
10
|
// custom rules loaded through oxlint's jsPlugins (ESLint v9 rule API): each
|
|
@@ -104,7 +106,9 @@ const plugin = {
|
|
|
104
106
|
'max-consecutive-line-comments': maxConsecutiveLineComments,
|
|
105
107
|
'no-comment-rule-override': noCommentRuleOverride,
|
|
106
108
|
'function-verb': functionVerb,
|
|
109
|
+
'no-restricted-words': noRestrictedWords,
|
|
107
110
|
'test-beside-module': testBesideModule,
|
|
111
|
+
'no-file-snapshot': noFileSnapshot,
|
|
108
112
|
'import-zones': importZones,
|
|
109
113
|
},
|
|
110
114
|
};
|