@zgeoff/oxlint-config 0.2.5 → 0.2.7

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 CHANGED
@@ -5,10 +5,11 @@ restriction-rule cherry-picks, comment-style enforcement via `@stylistic`, and a
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
7
  await arguments, awaits hidden inside a control-flow condition or a `&&`/`||`/`??` chain, every
8
- JSDoc block, any run of more than three line comments, and a repo config that loosens either comment
9
- rule. `zgeoff/function-verb` enforces the function-naming taxonomy from the shared agent guidelines,
10
- `zgeoff/test-beside-module` keeps each test file beside the module it tests, and
11
- `zgeoff/import-zones` fails an import that crosses a module boundary the repo declares.
8
+ `switch` statement, every JSDoc block, any run of more than three line comments, and a repo config
9
+ that loosens either comment rule. `zgeoff/function-verb` enforces the function-naming taxonomy from
10
+ the shared agent guidelines, `zgeoff/test-beside-module` keeps each test file beside the module it
11
+ tests, `zgeoff/import-zones` fails an import that crosses a module boundary the repo declares, and
12
+ `zgeoff/no-file-snapshot` bans file snapshots.
12
13
 
13
14
  ## Usage
14
15
 
@@ -85,6 +86,15 @@ first statement, so put the comment above it:
85
86
  A second test file for a module (`<name>.host.test.ts`) needs its own `<name>.host.<ext>`, so the
86
87
  rule reports it beside only `<name>.ts`.
87
88
 
89
+ ## `zgeoff/no-file-snapshot`
90
+
91
+ Snapshots are inline only, so the golden value sits in the test that pins it. The rule reports every
92
+ `toMatchSnapshot` and `toThrowErrorMatchingSnapshot`, which write a `.snap` file;
93
+ `toMatchInlineSnapshot` and `toThrowErrorMatchingInlineSnapshot` stay allowed. It also reports a
94
+ linted file that has a `__snapshots__/<file name>.snap` beside it, at the first statement, so a
95
+ file-level disable comment covers it. oxlint never visits a `.snap` file, so a snapshot file whose
96
+ test file is gone is not reported.
97
+
88
98
  ## `zgeoff/import-zones`
89
99
 
90
100
  Off unless a repo configures it. The options name zones (path globs, relative to the repo root) and
@@ -26,8 +26,6 @@ function buildZoneOptions(options) {
26
26
  match: makeGlobMatcher(globsByZone[name]),
27
27
  }));
28
28
 
29
- const buildEntries = (entries) => (entries ?? []).map((entry) => buildEntry(entry));
30
-
31
29
  return {
32
30
  zones,
33
31
  extensions: options.extensions ?? defaultExtensions,
@@ -40,6 +38,10 @@ function buildZoneOptions(options) {
40
38
  };
41
39
  }
42
40
 
41
+ function buildEntries(entries) {
42
+ return (entries ?? []).map((entry) => buildEntry(entry));
43
+ }
44
+
43
45
  function buildEntry(entry) {
44
46
  return {
45
47
  ...entry,
@@ -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;
package/oxlintrc.json CHANGED
@@ -25,11 +25,13 @@
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-switch": "error",
28
29
  "zgeoff/no-jsdoc": "error",
29
30
  "zgeoff/max-consecutive-line-comments": ["error", { "max": 3 }],
30
31
  "zgeoff/no-comment-rule-override": "error",
31
32
  "zgeoff/function-verb": "error",
32
33
  "zgeoff/test-beside-module": "error",
34
+ "zgeoff/no-file-snapshot": "error",
33
35
 
34
36
  // enforced comment style — line comments unless JSDoc or exclamation
35
37
  "@stylistic/multiline-comment-style": [
@@ -105,6 +107,10 @@
105
107
  "unicorn/number-literal-case": "off",
106
108
  "unicorn/filename-case": "off",
107
109
 
110
+ // one declaration per variable; a declaration list has no order to sort
111
+ "one-var": ["error", "never"],
112
+ "sort-vars": "off",
113
+
108
114
  // category-enabled rules that contradict this config's style: inline named
109
115
  // export declarations are the convention (group-exports demands the exact
110
116
  // form no-bare-named-exports bans), keys group by meaning not alphabet,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgeoff/oxlint-config",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Shareable oxlint config: strict categories, house-style rules, and a custom JS plugin",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,6 +16,7 @@
16
16
  "no-jsdoc.js",
17
17
  "no-comment-rule-override.js",
18
18
  "test-beside-module.js",
19
+ "no-file-snapshot.js",
19
20
  "import-zones.js",
20
21
  "import-zones-schema.js",
21
22
  "find-repo-root.js",
@@ -40,10 +41,10 @@
40
41
  "@stylistic/eslint-plugin": "5.10.0"
41
42
  },
42
43
  "devDependencies": {
43
- "@types/bun": "1.3.14",
44
- "typescript": "7.0.1-rc"
44
+ "@types/bun": "1.4.2",
45
+ "typescript": "7.0.2"
45
46
  },
46
47
  "peerDependencies": {
47
- "oxlint": ">=1.71.0"
48
+ "oxlint": ">=1.78.0"
48
49
  }
49
50
  }
package/plugin.js CHANGED
@@ -2,6 +2,7 @@ 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';
6
7
  import testBesideModule from './test-beside-module.js';
7
8
 
@@ -95,11 +96,17 @@ const plugin = {
95
96
  'Await into a named const instead of chaining it after a && / || / ?? operator.',
96
97
  ['LogicalExpression > AwaitExpression'],
97
98
  ),
99
+ 'no-switch': buildBanRule(
100
+ 'problem',
101
+ 'Branch with match(...).exhaustive() from ts-pattern, never switch.',
102
+ ['SwitchStatement'],
103
+ ),
98
104
  'no-jsdoc': noJSDoc,
99
105
  'max-consecutive-line-comments': maxConsecutiveLineComments,
100
106
  'no-comment-rule-override': noCommentRuleOverride,
101
107
  'function-verb': functionVerb,
102
108
  'test-beside-module': testBesideModule,
109
+ 'no-file-snapshot': noFileSnapshot,
103
110
  'import-zones': importZones,
104
111
  },
105
112
  };