@zgeoff/oxlint-config 0.2.6 → 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
@@ -8,7 +8,8 @@ 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, and `zgeoff/import-zones` fails an import that crosses a module boundary the repo declares.
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
@@ -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
@@ -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.6",
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,8 +41,8 @@
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
48
  "oxlint": ">=1.78.0"
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
 
@@ -105,6 +106,7 @@ const plugin = {
105
106
  'no-comment-rule-override': noCommentRuleOverride,
106
107
  'function-verb': functionVerb,
107
108
  'test-beside-module': testBesideModule,
109
+ 'no-file-snapshot': noFileSnapshot,
108
110
  'import-zones': importZones,
109
111
  },
110
112
  };