@noctcore/eslint-plugin-contracts 0.6.1 โ†’ 0.7.1

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
@@ -1,36 +1,73 @@
1
1
  # @noctcore/eslint-plugin-contracts
2
2
 
3
- Rules for shared **contract** conventions โ€” zod schema naming, wire-message discriminants, error
4
- stringification, direct `process.env` access, and money precision. Flat-config only, ESLint 9+.
3
+ Rules for shared **contract** conventions: zod schema naming, wire-message discriminants, error
4
+ stringification, direct `process.env` access and money precision, where drift between producer and
5
+ consumer breaks at runtime.
6
+
7
+ **Docs:** [noctcore.github.io/eslint-plugins/packages/contracts](https://noctcore.github.io/eslint-plugins/packages/contracts/)
8
+
9
+ Not a good fit for a codebase that does not use zod, or whose error handling is intentionally
10
+ untyped.
11
+
12
+ ## Requirements
13
+
14
+ - ESLint 9 or newer, flat config (`eslint.config.js`) only.
15
+ - `configs.recommended` registers the plugin and sets rule severities, nothing else. It sets no
16
+ `files` and no parser, so it applies to whatever files the rest of your config lints. To lint
17
+ TypeScript, add a `files` pattern and `@typescript-eslint/parser`, as in the quick start.
5
18
 
6
19
  ## Install
7
20
 
8
21
  ```sh
9
- bun add -D @noctcore/eslint-plugin-contracts # or npm i -D / pnpm add -D
22
+ bun add -D @noctcore/eslint-plugin-contracts @typescript-eslint/parser # or npm i -D / pnpm add -D
10
23
  ```
11
24
 
12
- ## Use
25
+ ## Quick start
13
26
 
14
27
  ```js
15
28
  // eslint.config.js
29
+ import tsParser from '@typescript-eslint/parser';
16
30
  import contracts from '@noctcore/eslint-plugin-contracts';
17
31
 
18
32
  export default [
19
- contracts.configs.recommended,
33
+ {
34
+ ...contracts.configs.recommended,
35
+ files: ['**/*.{ts,tsx}'],
36
+ languageOptions: { parser: tsParser },
37
+ },
38
+ // Optional: tune a preset rule's options.
39
+ {
40
+ files: ['**/*.{ts,tsx}'],
41
+ rules: {
42
+ 'noctcore-contracts/zod-schema-naming': ['error', { roleSuffixes: ['Event', 'Command', 'Query'] }],
43
+ 'noctcore-contracts/no-direct-process-env': ['error', { configModule: '@acme/config' }],
44
+ },
45
+ },
20
46
  ];
21
47
  ```
22
48
 
23
- Or wire rules individually:
49
+ ## Opt-in rules
24
50
 
25
- ```js
26
- import contracts from '@noctcore/eslint-plugin-contracts';
51
+ Four rules ship `off`: `require-registered-keys`, `env-var-schema-parity` and
52
+ `translation-key-exists` do nothing until their `sinks` / `schema` / `catalogs` options are set, and
53
+ `require-schema-parse-at-boundary` is a conservative syntactic slice of a type-aware concern.
27
54
 
55
+ ```js
56
+ // eslint.config.js
28
57
  export default [
58
+ // ...the quick start's entries, then:
29
59
  {
30
- plugins: { 'noctcore-contracts': contracts },
60
+ files: ['**/*.{ts,tsx}'],
61
+ languageOptions: { parser: tsParser },
31
62
  rules: {
32
- 'noctcore-contracts/zod-schema-naming': ['error', { roleSuffixes: ['Event', 'Command', 'Query'] }],
33
- 'noctcore-contracts/no-direct-process-env': ['error', { configModule: '@acme/config' }],
63
+ 'noctcore-contracts/require-registered-keys': ['error', {
64
+ sinks: [{ callee: 'localStorage.getItem', argIndex: 0 }],
65
+ }],
66
+ 'noctcore-contracts/env-var-schema-parity': ['error', { schema: '.env.example' }],
67
+ 'noctcore-contracts/translation-key-exists': ['error', {
68
+ catalogs: [{ file: 'locales/en/common.json', namespace: 'common' }],
69
+ }],
70
+ 'noctcore-contracts/require-schema-parse-at-boundary': 'error',
34
71
  },
35
72
  },
36
73
  ];
@@ -38,24 +75,29 @@ export default [
38
75
 
39
76
  ## Rules
40
77
 
41
- Legend: ๐Ÿ”ง = autofixable ยท ๐Ÿ’ค = ships inert / `off` in `recommended` (enable + configure explicitly).
42
-
43
- | Rule | Description | ๐Ÿ”ง | ๐Ÿ’ค |
44
- | --- | --- | --- | --- |
45
- | [`zod-schema-naming`](./docs/rules/zod-schema-naming.md) | Exported zod schema must be a PascalCase `*Schema` const with a sibling `z.infer` type. | | |
46
- | [`wire-message-naming`](./docs/rules/wire-message-naming.md) | A role-suffixed schema's `type: z.literal(...)` must be kebab-case of its name minus the suffix. | ๐Ÿ”ง | |
47
- | [`no-error-stringify`](./docs/rules/no-error-stringify.md) | Ban `${error}` / `error.toString()` / `error + ""` โ€” they drop the cause chain. | | |
48
- | [`no-direct-process-env`](./docs/rules/no-direct-process-env.md) | Ban direct `process.env`; require a typed config accessor. | | |
49
- | [`money-must-be-decimal`](./docs/rules/money-must-be-decimal.md) | Money-named fields typed `: number` are banned; require a Decimal money type. | | |
50
- | [`require-error-cause`](./docs/rules/require-error-cause.md) | Re-throwing a new error inside `catch` must forward the caught error as `{ cause }`. | ๐Ÿ”ง | |
51
- | [`restrict-throw-to-taxonomy`](./docs/rules/restrict-throw-to-taxonomy.md) | `throw` only allowlisted error classes; ban throwing non-Error values. | | |
52
- | [`require-registered-keys`](./docs/rules/require-registered-keys.md) | Key/name argument of a configured sink API must be an imported constant, not a raw string. | | ๐Ÿ’ค |
53
- | [`env-var-schema-parity`](./docs/rules/env-var-schema-parity.md) | `process.env.FOO` / `import.meta.env.FOO` keys must be declared in a schema file. | | ๐Ÿ’ค |
54
- | [`require-schema-parse-at-boundary`](./docs/rules/require-schema-parse-at-boundary.md) | Ban `JSON.parse(...) as T` / `(await res.json()) as T`; parse boundary data at runtime. | | ๐Ÿ’ค |
55
- | [`schema-enum-field-consistency`](./docs/rules/schema-enum-field-consistency.md) | A field that is an enum in one zod object schema must not be `z.string()` in another schema of the same module. | | |
56
- | [`fetch-must-check-ok`](./docs/rules/fetch-must-check-ok.md) | A fetch response must be checked with `.ok` or a status comparison before `.json()` parses its body. | | |
57
- | [`translation-key-exists`](./docs/rules/translation-key-exists.md) | A static i18next / react-i18next key (`t(...)`, `i18n.t(...)`, `<Trans i18nKey>`) must exist in the catalog of the namespace in scope. | | ๐Ÿ’ค |
58
-
59
- The ๐Ÿ’ค rules ship `off` in `recommended`: `require-registered-keys`, `env-var-schema-parity` and
60
- `translation-key-exists` do nothing until their `sinks` / `schema` / `catalogs` options are set, and `require-schema-parse-at-boundary` is a
61
- conservative syntactic slice of a type-aware concern. Turn them on explicitly once configured.
78
+ <!-- begin generated rules -->
79
+ <!-- Generated by `bun run docs:readmes` from each rule's meta. Do not edit by hand. -->
80
+
81
+ โœ… in `recommended` (error) ยท โš™๏ธ needs options ยท ๐Ÿ”ง `--fix` ยท ๐Ÿ’ก suggestions ยท ๐Ÿ’ญ needs type info ยท โŒ deprecated
82
+
83
+ | Rule | Description | โœ… | โš™๏ธ | ๐Ÿ”ง | ๐Ÿ’ก | ๐Ÿ’ญ |
84
+ | --- | --- | :-: | :-: | :-: | :-: | :-: |
85
+ | [`env-var-schema-parity`](https://noctcore.github.io/eslint-plugins/rules/contracts/env-var-schema-parity/) | Require every `process.env.FOO` / `import.meta.env.FOO` key to be declared in a schema file (`.env.example` or a zod-env module), so config access and config declaration cannot drift apart. | | โš™๏ธ | | | |
86
+ | [`fetch-must-check-ok`](https://noctcore.github.io/eslint-plugins/rules/contracts/fetch-must-check-ok/) | Require a fetch response to be checked with `.ok` or a status comparison before `.json()` parses its body. | โœ… | | | | |
87
+ | [`money-must-be-decimal`](https://noctcore.github.io/eslint-plugins/rules/contracts/money-must-be-decimal/) | Disallow monetary values typed as the JS primitive `number`. Money-named fields explicitly typed `: number` lose precision to float rounding; use a Decimal money type instead. | โœ… | | | | |
88
+ | [`no-direct-process-env`](https://noctcore.github.io/eslint-plugins/rules/contracts/no-direct-process-env/) | Disallow direct `process.env` access. Force every consumer through a typed, validated config accessor so a missing variable fails at boot, not at use. | โœ… | | | | |
89
+ | [`no-error-stringify`](https://noctcore.github.io/eslint-plugins/rules/contracts/no-error-stringify/) | Disallow stringifying an error with bare `${error}` interpolation, `error.toString()`, or `error + ""`. These drop the cause chain. Use `error instanceof Error ? error.message : String(error)` instead. | โœ… | | | | |
90
+ | [`require-error-cause`](https://noctcore.github.io/eslint-plugins/rules/contracts/require-error-cause/) | Require re-thrown errors inside a `catch` to forward the caught error as `{ cause }`. A `throw new SomeError(...)` that omits the cause severs the chain to the original failure. | โœ… | | ๐Ÿ”ง | | |
91
+ | [`require-registered-keys`](https://noctcore.github.io/eslint-plugins/rules/contracts/require-registered-keys/) | Require the key/name argument of configured sink APIs (storage, event channels, cache keys) to be an imported constant from a registry module, not a raw string literal. | | โš™๏ธ | | | |
92
+ | [`require-schema-parse-at-boundary`](https://noctcore.github.io/eslint-plugins/rules/contracts/require-schema-parse-at-boundary/) | Disallow asserting external boundary data with `as T` instead of parsing it at runtime. Flags casts of `JSON.parse`, `res.json()`, web storage, URL search params, message-event data and LLM tool input, directly or through a `const`; use a zod/valibot parse. | | | | | |
93
+ | [`restrict-throw-to-taxonomy`](https://noctcore.github.io/eslint-plugins/rules/contracts/restrict-throw-to-taxonomy/) | Restrict `throw` to an approved error taxonomy. Flags throwing a non-allowlisted error class and throwing a non-Error value (string, object, number, ...). | โœ… | | | | |
94
+ | [`schema-enum-field-consistency`](https://noctcore.github.io/eslint-plugins/rules/contracts/schema-enum-field-consistency/) | Disallow a zod field that is an enum in one object schema of a module from being `z.string()` in another, which widens the wire type every consumer then narrows by hand. | โœ… | | | | |
95
+ | [`translation-key-exists`](https://noctcore.github.io/eslint-plugins/rules/contracts/translation-key-exists/) | Require every static i18next / react-i18next translation key (`t(...)`, `i18n.t(...)`, `<Trans i18nKey>`) to exist in the catalog of the namespace in scope. | | โš™๏ธ | | | |
96
+ | [`wire-message-naming`](https://noctcore.github.io/eslint-plugins/rules/contracts/wire-message-naming/) | A message-schema const ending in a role suffix (default Event/Command/Query) whose zod object declares `type: z.literal(...)` must set that literal to kebab-case(const name minus its role suffix). | โœ… | | ๐Ÿ”ง | | |
97
+ | [`zod-schema-naming`](https://noctcore.github.io/eslint-plugins/rules/contracts/zod-schema-naming/) | Every exported zod schema is a PascalCase const suffixed `Schema`, paired with a same-named inferred type (`export type Foo = z.infer<typeof FooSchema>`). | โœ… | | | | |
98
+ <!-- end generated rules -->
99
+
100
+ ## Severity policy
101
+
102
+ Every rule is `error` or `off`, never `warn`: a warning is a rule nobody obeys. See the
103
+ [severity policy](https://noctcore.github.io/eslint-plugins/getting-started/#severity-policy).