@opetope/lint 0.15.0 → 0.16.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/CHANGELOG.md +14 -0
- package/README.md +73 -122
- package/dist/command-hooks.js +1 -1
- package/dist/command-hooks.js.map +1 -1
- package/dist/context-members.d.ts +4 -3
- package/dist/context-members.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/retired-vocabulary-messages.d.ts +1 -1
- package/dist/retired-vocabulary-messages.js +1 -1
- package/dist/retired-vocabulary-messages.js.map +1 -1
- package/dist/rules/define-feature-property-order.js +1 -1
- package/dist/rules/define-feature-property-order.js.map +1 -1
- package/dist/rules/no-command-in-deps.js +1 -1
- package/dist/rules/no-command-in-deps.js.map +1 -1
- package/dist/rules/no-retired-vocabulary.js +1 -1
- package/dist/rules/no-retired-vocabulary.js.map +1 -1
- package/dist/rules/no-write-after-source-write.js.map +1 -1
- package/dist/rules/require-static-feature.js +1 -1
- package/dist/rules/require-static-feature.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @opetope/lint
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f3e4e4a: Make npm READMEs self-contained, pin installation examples to the documented version and link to the same release's
|
|
8
|
+
public documentation. Clarify timer cancellation and physical cleanup, switch-navigation persistence and screen
|
|
9
|
+
matching, and correct timer examples so cancellation can finish their owned bodies.
|
|
10
|
+
- f66744b: `CommandHook.dismiss()` clears the outcome a consumer shows until its next run settles; it belongs to that outcome, so a timer of an old outcome never clears a new one, and a `CommandHook` built by hand in a test now needs it (D543). Kit `failureOf(status)` reads a domain refusal returned as a `Result` and a thrown `failed` outcome for one place in the UI, `null` while a run is in flight (D543).
|
|
11
|
+
`opetope/no-command-in-deps` offers `hook.dismiss` as the dependency of a callback that reads it (D543).
|
|
12
|
+
- fedf20a: `opetope/define-feature-property-order` no longer points its message and description at the removed specification: the
|
|
13
|
+
message cites the decisions behind the order (D542, D280, D477), and the description says that the sections of
|
|
14
|
+
`defineFeature` keep one declaration order.
|
|
15
|
+
- c88bc3d: A lazy header loads its body file, `body: () => import('./feature.body')`, whose default export is `defineFeature.body(header, { own, exports, provides })`, and reads its exports from that export; a `FeatureBody` carries the id of the header it implements. The header is a `LazyFeature`, whose fourth explicit type argument is now the loaded value rather than the exports; `defineFeature.body` refuses a feature without a body loader, and `FeatureBodyOf` is removed — `opetope/no-retired-vocabulary` names the replacement (D542).
|
|
16
|
+
|
|
3
17
|
## 0.15.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
# `@opetope/lint`
|
|
2
2
|
|
|
3
|
-
ESLint rules for
|
|
3
|
+
ESLint and Oxlint rules for Opetope authoring: model writes, feature declarations, hook dependencies and package
|
|
4
|
+
boundaries. Use the recommended rules first, then add layer rules when your project names its directories.
|
|
5
|
+
The plugin complements TypeScript and runtime validation and needs no Opetope runtime packages.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
9
|
+
For an ESLint flat configuration:
|
|
10
|
+
|
|
7
11
|
```sh
|
|
8
|
-
npm install --save-exact --save-dev @opetope/lint
|
|
12
|
+
npm install --save-exact --save-dev @opetope/lint@0.16.0 'eslint@^9' '@typescript-eslint/parser@^8'
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`@typescript-eslint/scope-manager`, have no peers.
|
|
15
|
-
|
|
16
|
-
Use matching exact Opetope versions; for release candidates, install every Opetope package from `@next`.
|
|
17
|
-
Packages are ESM-only and support Node 20.19+. React integrations support React and React DOM 19.
|
|
15
|
+
The API is experimental. Keep all Opetope packages at the same exact version. The commands pin the API shown
|
|
16
|
+
here; for prereleases, select `@next` on every Opetope package. Packages are ESM-only and support Node 20.19+.
|
|
17
|
+
ESLint 9 and `@typescript-eslint/parser` 8 are optional peers: an ESLint host installs both explicitly.
|
|
18
18
|
|
|
19
19
|
## Example
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
feature, host, React entry and cleanup in one visible module.
|
|
21
|
+
Put this in `eslint.config.mjs`, then run `npx eslint src`:
|
|
23
22
|
|
|
24
23
|
```js
|
|
25
|
-
// eslint.config.mjs
|
|
26
24
|
import opetope from '@opetope/lint';
|
|
27
25
|
import parser from '@typescript-eslint/parser';
|
|
28
26
|
|
|
@@ -35,126 +33,79 @@ export default [
|
|
|
35
33
|
];
|
|
36
34
|
```
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
`recommended` supplies the `opetope` plugin and its authoring rules. `configs.layers({ models, ui, integration })`
|
|
37
|
+
adds rules using the globs you supply; `configs.internalImports({ allow })` names any trusted host integration
|
|
38
|
+
allowed to use the implementation ABI. The [lint reference](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md) lists every rule,
|
|
39
|
+
its default severity, layer configuration and the Oxlint setup.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
[Guides](../runtime/docs/guides/index.md) show individual tasks. Documentation is shipped with
|
|
42
|
-
`@opetope/runtime` in `docs/`, so an installed application can read it without access to this repository.
|
|
41
|
+
## Oxlint
|
|
43
42
|
|
|
44
|
-
For
|
|
45
|
-
`npm run ci:test --workspace @opetope/lint` where provided. The root `npm run check` performs full acceptance;
|
|
46
|
-
`docs/maintainers/contributing.md` in the checkout describes the build and package checks.
|
|
43
|
+
For an Oxlint host, use the plugin without the ESLint peers:
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
```sh
|
|
46
|
+
npm install --save-exact --save-dev @opetope/lint@0.16.0 'oxlint@^1.80.0'
|
|
47
|
+
```
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
Put this in `.oxlintrc.json`, then run `npx oxlint src`. Both paths are relative to that config file. The shipped
|
|
50
|
+
fragment supplies severities; `jsPlugins` registers the plugin. Under Oxlint the plugin loads no ESLint, parser or
|
|
51
|
+
TypeScript.
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"extends": ["./node_modules/@opetope/lint/oxlintrc.json"],
|
|
56
|
+
"jsPlugins": [{ "name": "opetope", "specifier": "./node_modules/@opetope/lint/dist/index.js" }]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
[See `recommended`](../runtime/docs/reference/lint.md#recommended).
|
|
60
|
+
## Documentation
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
[
|
|
62
|
+
- [Start: build, mount and close one feature](https://unpkg.com/@opetope/runtime@0.16.0/docs/start.md).
|
|
63
|
+
- [Guides: recipes for individual tasks](https://unpkg.com/@opetope/runtime@0.16.0/docs/guides/index.md).
|
|
64
|
+
- [API reference](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md): signatures, options and lifecycle guarantees.
|
|
65
|
+
|
|
66
|
+
The links open the Markdown files from this release. The full documentation also ships in `@opetope/runtime/docs`;
|
|
67
|
+
with Runtime installed, start at `node_modules/@opetope/runtime/docs/start.md`.
|
|
68
|
+
|
|
69
|
+
## Rule reference
|
|
70
|
+
|
|
71
|
+
Each rule's help link points to its canonical contract and examples.
|
|
72
|
+
|
|
73
|
+
- <a id="capture-command-cleanup"></a>[`capture-command-cleanup`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#capture-command-cleanup)
|
|
74
|
+
- <a id="define-feature-property-order"></a>[`define-feature-property-order`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#define-feature-property-order)
|
|
75
|
+
- <a id="enabled-predicate"></a>[`enabled-predicate`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#enabled-predicate)
|
|
76
|
+
- <a id="id-naming"></a>[`id-naming`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#id-naming)
|
|
77
|
+
- <a id="layer-placement"></a>[`layer-placement`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#layer-placement)
|
|
78
|
+
- <a id="no-behavior-in-integration"></a>[`no-behavior-in-integration`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-behavior-in-integration)
|
|
79
|
+
- <a id="no-command-in-deps"></a>[`no-command-in-deps`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-command-in-deps)
|
|
80
|
+
- <a id="no-dead-abort-check"></a>[`no-dead-abort-check`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-dead-abort-check)
|
|
81
|
+
- <a id="no-event-without-emit"></a>[`no-event-without-emit`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-event-without-emit)
|
|
82
|
+
- <a id="no-inline-requires-models"></a>[`no-inline-requires-models`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-inline-requires-models)
|
|
83
|
+
- <a id="no-internal-imports"></a>[`no-internal-imports`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-internal-imports)
|
|
84
|
+
- <a id="no-redundant-const-tuple"></a>[`no-redundant-const-tuple`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-redundant-const-tuple)
|
|
85
|
+
- <a id="no-retired-vocabulary"></a>[`no-retired-vocabulary`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-retired-vocabulary)
|
|
86
|
+
- <a id="no-single-command-lane"></a>[`no-single-command-lane`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-single-command-lane)
|
|
87
|
+
- <a id="no-snapshot-in-update"></a>[`no-snapshot-in-update`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-snapshot-in-update)
|
|
88
|
+
- <a id="no-snapshot-read-in-render"></a>[`no-snapshot-read-in-render`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-snapshot-read-in-render)
|
|
89
|
+
- <a id="no-subscribe-outside-models"></a>[`no-subscribe-outside-models`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-subscribe-outside-models)
|
|
90
|
+
- <a id="no-write-after-source-write"></a>[`no-write-after-source-write`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#no-write-after-source-write)
|
|
91
|
+
- <a id="prefer-derive"></a>[`prefer-derive`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#prefer-derive)
|
|
92
|
+
- <a id="prefer-effect-current"></a>[`prefer-effect-current`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#prefer-effect-current)
|
|
93
|
+
- <a id="prefer-lazy-body-record"></a>[`prefer-lazy-body-record`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#prefer-lazy-body-record)
|
|
94
|
+
- <a id="prefer-model-selection"></a>[`prefer-model-selection`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#prefer-model-selection)
|
|
95
|
+
- <a id="prefer-read"></a>[`prefer-read`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#prefer-read)
|
|
96
|
+
- <a id="prefer-when-over-enabled"></a>[`prefer-when-over-enabled`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#prefer-when-over-enabled)
|
|
97
|
+
- <a id="require-declared-models"></a>[`require-declared-models`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#require-declared-models)
|
|
98
|
+
- <a id="require-literal-id"></a>[`require-literal-id`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#require-literal-id)
|
|
99
|
+
- <a id="require-static-feature"></a>[`require-static-feature`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#require-static-feature)
|
|
100
|
+
- <a id="scope-builder-releases-subscriptions"></a>[`scope-builder-releases-subscriptions`](https://unpkg.com/@opetope/runtime@0.16.0/docs/reference/lint.md#scope-builder-releases-subscriptions)
|
|
62
101
|
|
|
102
|
+
<a id="opetopelint"></a>
|
|
103
|
+
<a id="usage"></a>
|
|
104
|
+
<a id="configs"></a>
|
|
105
|
+
<a id="recommended"></a>
|
|
106
|
+
<a id="layers"></a>
|
|
63
107
|
<a id="internalimports"></a>
|
|
64
|
-
[See `internalImports`](../runtime/docs/reference/lint.md#internalimports).
|
|
65
|
-
|
|
66
108
|
<a id="rules"></a>
|
|
67
|
-
[See Rules](../runtime/docs/reference/lint.md#rules).
|
|
68
|
-
|
|
69
|
-
<a id="no-internal-imports"></a>
|
|
70
|
-
[See `no-internal-imports`](../runtime/docs/reference/lint.md#no-internal-imports).
|
|
71
|
-
|
|
72
|
-
<a id="enabled-predicate"></a>
|
|
73
|
-
[See `enabled-predicate`](../runtime/docs/reference/lint.md#enabled-predicate).
|
|
74
|
-
|
|
75
|
-
<a id="define-feature-property-order"></a>
|
|
76
|
-
[See `define-feature-property-order`](../runtime/docs/reference/lint.md#define-feature-property-order).
|
|
77
|
-
|
|
78
|
-
<a id="no-redundant-const-tuple"></a>
|
|
79
|
-
[See `no-redundant-const-tuple`](../runtime/docs/reference/lint.md#no-redundant-const-tuple).
|
|
80
|
-
|
|
81
|
-
<a id="require-declared-models"></a>
|
|
82
|
-
[See `require-declared-models`](../runtime/docs/reference/lint.md#require-declared-models).
|
|
83
|
-
|
|
84
|
-
<a id="require-literal-id"></a>
|
|
85
|
-
[See `require-literal-id`](../runtime/docs/reference/lint.md#require-literal-id).
|
|
86
|
-
|
|
87
|
-
<a id="id-naming"></a>
|
|
88
|
-
[See `id-naming`](../runtime/docs/reference/lint.md#id-naming).
|
|
89
|
-
|
|
90
|
-
<a id="layer-placement"></a>
|
|
91
|
-
[See `layer-placement`](../runtime/docs/reference/lint.md#layer-placement).
|
|
92
|
-
|
|
93
|
-
<a id="no-snapshot-read-in-render"></a>
|
|
94
|
-
[See `no-snapshot-read-in-render`](../runtime/docs/reference/lint.md#no-snapshot-read-in-render).
|
|
95
|
-
|
|
96
|
-
<a id="no-snapshot-in-update"></a>
|
|
97
|
-
[See `no-snapshot-in-update`](../runtime/docs/reference/lint.md#no-snapshot-in-update).
|
|
98
|
-
|
|
99
|
-
<a id="no-command-in-deps"></a>
|
|
100
|
-
[See `no-command-in-deps`](../runtime/docs/reference/lint.md#no-command-in-deps).
|
|
101
|
-
|
|
102
|
-
<a id="no-subscribe-outside-models"></a>
|
|
103
|
-
[See `no-subscribe-outside-models`](../runtime/docs/reference/lint.md#no-subscribe-outside-models).
|
|
104
|
-
|
|
105
|
-
<a id="scope-builder-releases-subscriptions"></a>
|
|
106
|
-
[See `scope-builder-releases-subscriptions`](../runtime/docs/reference/lint.md#scope-builder-releases-subscriptions).
|
|
107
|
-
|
|
108
|
-
<a id="prefer-effect-current"></a>
|
|
109
|
-
[See `prefer-effect-current`](../runtime/docs/reference/lint.md#prefer-effect-current).
|
|
110
|
-
|
|
111
|
-
<a id="prefer-read"></a>
|
|
112
|
-
[See `prefer-read`](../runtime/docs/reference/lint.md#prefer-read).
|
|
113
|
-
|
|
114
|
-
<a id="no-write-after-source-write"></a>
|
|
115
|
-
[See `no-write-after-source-write`](../runtime/docs/reference/lint.md#no-write-after-source-write).
|
|
116
|
-
|
|
117
|
-
<a id="capture-command-cleanup"></a>
|
|
118
|
-
[See `capture-command-cleanup`](../runtime/docs/reference/lint.md#capture-command-cleanup).
|
|
119
|
-
|
|
120
|
-
<a id="prefer-lazy-body-record"></a>
|
|
121
|
-
[See `prefer-lazy-body-record`](../runtime/docs/reference/lint.md#prefer-lazy-body-record).
|
|
122
|
-
|
|
123
|
-
<a id="prefer-model-selection"></a>
|
|
124
|
-
[See `prefer-model-selection`](../runtime/docs/reference/lint.md#prefer-model-selection).
|
|
125
|
-
|
|
126
|
-
<a id="no-retired-vocabulary"></a>
|
|
127
|
-
[See `no-retired-vocabulary`](../runtime/docs/reference/lint.md#no-retired-vocabulary).
|
|
128
|
-
|
|
129
|
-
<a id="prefer-derive"></a>
|
|
130
|
-
[See `prefer-derive`](../runtime/docs/reference/lint.md#prefer-derive).
|
|
131
|
-
|
|
132
|
-
<a id="no-behavior-in-integration"></a>
|
|
133
|
-
[See `no-behavior-in-integration`](../runtime/docs/reference/lint.md#no-behavior-in-integration).
|
|
134
|
-
|
|
135
|
-
<a id="no-single-command-lane"></a>
|
|
136
|
-
[See `no-single-command-lane`](../runtime/docs/reference/lint.md#no-single-command-lane).
|
|
137
|
-
|
|
138
|
-
<a id="no-dead-abort-check"></a>
|
|
139
|
-
[See `no-dead-abort-check`](../runtime/docs/reference/lint.md#no-dead-abort-check).
|
|
140
|
-
|
|
141
|
-
<a id="no-event-without-emit"></a>
|
|
142
|
-
[See `no-event-without-emit`](../runtime/docs/reference/lint.md#no-event-without-emit).
|
|
143
|
-
|
|
144
|
-
<a id="require-static-feature"></a>
|
|
145
|
-
[See `require-static-feature`](../runtime/docs/reference/lint.md#require-static-feature).
|
|
146
|
-
|
|
147
|
-
<a id="prefer-when-over-enabled"></a>
|
|
148
|
-
[See `prefer-when-over-enabled`](../runtime/docs/reference/lint.md#prefer-when-over-enabled).
|
|
149
|
-
|
|
150
|
-
<a id="no-inline-requires-models"></a>
|
|
151
|
-
[See `no-inline-requires-models`](../runtime/docs/reference/lint.md#no-inline-requires-models).
|
|
152
|
-
|
|
153
109
|
<a id="coexistence-with-key-sorting"></a>
|
|
154
|
-
[See Coexistence with key sorting](../runtime/docs/reference/lint.md#coexistence-with-key-sorting).
|
|
155
|
-
|
|
156
110
|
<a id="running-the-rules-under-oxlint"></a>
|
|
157
|
-
[See Running the rules under Oxlint](../runtime/docs/reference/lint.md#running-the-rules-under-oxlint).
|
|
158
|
-
|
|
159
111
|
<a id="checks"></a>
|
|
160
|
-
Contributor checks: `docs/maintainers/contributing.md` in a contributor checkout.
|
package/dist/command-hooks.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AST_NODE_TYPES as o}from"@typescript-eslint/types";import{apiName as v,unwrap as p,localValue as I,variableOf as d}from"./model-bindings.js";const O=new Set(["inFlight","outcome","run"]),S=["inFlight","outcome"],l=new Set(["selection"]),h=2,y={member:void 0,optional:!1,text:""},g={accesses:[],opaque:!0};function c(n){const{parent:e}=n;if(!(e.type!==o.MemberExpression||e.object!==n||e.computed)&&e.property.type===o.Identifier)return{key:e.property.name,node:e,optional:e.optional}}function
|
|
1
|
+
import{AST_NODE_TYPES as o}from"@typescript-eslint/types";import{apiName as v,unwrap as p,localValue as I,variableOf as d}from"./model-bindings.js";const O=new Set(["dismiss","inFlight","outcome","run"]),S=["inFlight","outcome"],l=new Set(["selection"]),h=2,y={member:void 0,optional:!1,text:""},g={accesses:[],opaque:!0};function c(n){const{parent:e}=n;if(!(e.type!==o.MemberExpression||e.object!==n||e.computed)&&e.property.type===o.Identifier)return{key:e.property.name,node:e,optional:e.optional}}function s(n,e,i){return e===void 0||!O.has(e.key)?y:{member:e.key,optional:i||e.optional,text:n.getText(e.node)}}function m(n,e,i){const t=c(e);return!i.record&&i.key===void 0?s(n,t,!1):t===void 0?y:i.key===void 0||t.key===i.key?s(n,c(t.node),t.optional):void 0}function R(n,e){return n.range[0]>=e.range[0]&&n.range[1]<=e.range[1]}function x(n,e,i){if(i===void 0)return g;const t=[];let r=!1;for(const{identifier:f}of e.variable.references){if(f.type!==o.Identifier||!R(f,i))continue;const u=m(n,f,e);u!==void 0&&(u.member===void 0?r=!0:t.push(u))}return{accesses:t,opaque:r}}function A(n,e){for(const{identifier:i}of e.variable.references)if(i.type===o.Identifier&&m(n,i,e)?.member==="run")return!0;return!1}function k(n,e){return e===null?"":n.get(e)??""}function E(n,e){return!e.proved||A(n,e)?e:void 0}function M(n,e,i){const t=d(n,i),r=k(e,t);if(t===null||r==="")return;const f=l.has(r);return E(n,{key:void 0,proved:r==="candidate",record:f,variable:t})}function T(n){const e=p(n.object);if(!(n.computed||n.property.type!==o.Identifier))return e.type===o.Identifier?{key:n.property.name,object:e}:void 0}function _(n,e,i){const t=T(i);if(t===void 0)return;const r=d(n,t.object),f=k(e,r);if(!(r===null||!l.has(f)))return E(n,{key:t.key,proved:f==="selection",record:!1,variable:r})}function j(n,e,i){const t=p(i);return t.type===o.Identifier?M(n,e,t):t.type===o.MemberExpression?_(n,e,t):void 0}function a(n,e,i,t){if(e.type!==o.Identifier)return;const r=d(n,e);r!==null&&i.set(r,t)}function B(n,e,i,t,r){if(e.type!==o.ObjectPattern){a(n,e,i,t);return}for(const f of e.properties)f.type===o.Property&&a(n,f.value,i,r)}function C(n,e){if(e.parent.kind!=="const"||e.init===null)return;const i=I(n,e.init);return i.type===o.CallExpression?i:void 0}function D(n,e,i){const t=C(n,e);if(t===void 0)return;const r=v(n,t.callee,"@opetope/react");r==="useCommand"?a(n,e.id,i,"hook"):r==="useModel"&&t.arguments.length>=h&&B(n,e.id,i,"selection","candidate")}export{S as STATUS_MEMBERS,j as bindingAt,D as classifyDeclaration,x as usageIn};
|
|
2
2
|
//# sourceMappingURL=command-hooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-hooks.js","sources":["../src/command-hooks.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { apiName, localValue, unwrap, variableOf } from './model-bindings';\nimport type { SourceCode, Variable } from './rule';\n\n/** What a name holds: one hook, a selection whose fields may be hooks, or one such field (D388). */\ntype HookKind = 'candidate' | 'hook' | 'selection';\ntype HookKinds = Map<Variable, HookKind>;\n/**\n * What a dependency element names: one hook (no key, not a record), one key of a record, or the record itself,\n * which is a fresh object on every render.\n */\ntype Binding = {\n readonly key: string | undefined;\n readonly proved: boolean;\n readonly record: boolean;\n readonly variable: Variable;\n};\n/** One read of the binding: the member it ends in, and the text the author wrote to reach it. */\ntype Access = { readonly member: string | undefined; readonly optional: boolean; readonly text: string };\n/** `opaque` is a use no member replaces: the object passed on, destructured, or read past a member we know. */\ntype Usage = { readonly accesses: readonly Access[]; readonly opaque: boolean };\ntype Step = { readonly key: string; readonly node: TSESTree.MemberExpression; readonly optional: boolean };\ntype Occurrence = TSESTree.Identifier | TSESTree.MemberExpression;\n\nconst HOOK_MEMBERS = new Set(['inFlight', 'outcome', 'run']);\nconst STATUS_MEMBERS = ['inFlight', 'outcome'];\nconst RECORD_KINDS = new Set(['selection']);\nconst SELECTION_ARITY = 2;\nconst OPAQUE: Access = { member: undefined, optional: false, text: '' };\n/** A callback this module cannot read is not proof of anything, so its binding takes no fix and no suggestion. */\nconst UNREAD: Usage = { accesses: [], opaque: true };\n\n/** One written step off an occurrence: `.save`, with the `?.` the author may have put in front of it. */\nfunction step(node: Occurrence): Step | undefined {\n const { parent } = node;\n\n if (parent.type !== AST_NODE_TYPES.MemberExpression || parent.object !== node || parent.computed) return undefined;\n\n if (parent.property.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return { key: parent.property.name, node: parent, optional: parent.optional };\n}\n\n/** A step that lands on a member this rule knows; anything else is a use it must not rewrite. */\nfunction accessAt(source: SourceCode, member: Step | undefined, over: boolean): Access {\n if (member === undefined || !HOOK_MEMBERS.has(member.key)) return OPAQUE;\n\n return { member: member.key, optional: over || member.optional, text: source.getText(member.node) };\n}\n\n/** What this occurrence reads on the binding: one step for a hook, two for a record — key, then member. */\nfunction accessOf(source: SourceCode, node: TSESTree.Identifier, binding: Binding): Access | undefined {\n const first = step(node);\n\n if (!binding.record && binding.key === undefined) return accessAt(source, first, false);\n\n if (first === undefined) return OPAQUE;\n\n if (binding.key === undefined) return accessAt(source, step(first.node), first.optional);\n\n return first.key === binding.key ? accessAt(source, step(first.node), first.optional) : undefined;\n}\n\nfunction within(node: TSESTree.Node, callback: TSESTree.Node): boolean {\n return node.range[0] >= callback.range[0] && node.range[1] <= callback.range[1];\n}\n\n/** Every read of the binding inside one callback, and whether any of them takes the object as a whole. */\nfunction usageIn(source: SourceCode, binding: Binding, callback: TSESTree.Node | undefined): Usage {\n if (callback === undefined) return UNREAD;\n\n const accesses: Access[] = [];\n let opaque = false;\n\n for (const { identifier } of binding.variable.references) {\n if (identifier.type !== AST_NODE_TYPES.Identifier || !within(identifier, callback)) continue;\n\n const access = accessOf(source, identifier, binding);\n\n if (access === undefined) continue;\n if (access.member === undefined) opaque = true;\n else accesses.push(access);\n }\n\n return { accesses, opaque };\n}\n\n/**\n * A selected field is a command hook where the code calls or reads `run` on it. A status name alone proves\n * nothing: plain data may carry one, and this package reads shapes rather than types.\n */\nfunction provesRun(source: SourceCode, binding: Binding): boolean {\n for (const { identifier } of binding.variable.references) {\n if (identifier.type !== AST_NODE_TYPES.Identifier) continue;\n\n if (accessOf(source, identifier, binding)?.member === 'run') return true;\n }\n\n return false;\n}\n\nfunction kindOf(kinds: HookKinds, variable: Variable | null): string {\n return variable === null ? '' : (kinds.get(variable) ?? '');\n}\n\n/** A kind that is a claim about a selected field keeps the binding only where the code proves `run` on it. */\nfunction provedBinding(source: SourceCode, binding: Binding): Binding | undefined {\n return !binding.proved || provesRun(source, binding) ? binding : undefined;\n}\n\nfunction namedBinding(source: SourceCode, kinds: HookKinds, node: TSESTree.Identifier): Binding | undefined {\n const variable = variableOf(source, node);\n const kind = kindOf(kinds, variable);\n\n if (variable === null || kind === '') return undefined;\n\n const record = RECORD_KINDS.has(kind);\n\n return provedBinding(source, { key: undefined, proved: kind === 'candidate', record, variable });\n}\n\n/** `hooks.save`: the plain name it is read from and the key it reads, when both of them are written as names. */\nfunction keyRead(node: TSESTree.MemberExpression): { key: string; object: TSESTree.Identifier } | undefined {\n const object = unwrap(node.object);\n\n if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return object.type === AST_NODE_TYPES.Identifier ? { key: node.property.name, object } : undefined;\n}\n\nfunction keyedBinding(source: SourceCode, kinds: HookKinds, node: TSESTree.MemberExpression): Binding | undefined {\n const read = keyRead(node);\n\n if (read === undefined) return undefined;\n\n const variable = variableOf(source, read.object);\n const kind = kindOf(kinds, variable);\n\n if (variable === null || !RECORD_KINDS.has(kind)) return undefined;\n\n return provedBinding(source, { key: read.key, proved: kind === 'selection', record: false, variable });\n}\n\n/** The binding a dependency element names as a whole; a member of one is a member, and this returns nothing. */\nfunction bindingAt(source: SourceCode, kinds: HookKinds, element: TSESTree.Node): Binding | undefined {\n const value = unwrap(element);\n\n if (value.type === AST_NODE_TYPES.Identifier) return namedBinding(source, kinds, value);\n\n return value.type === AST_NODE_TYPES.MemberExpression ? keyedBinding(source, kinds, value) : undefined;\n}\n\nfunction mark(source: SourceCode, node: TSESTree.Node, kinds: HookKinds, kind: HookKind): void {\n if (node.type !== AST_NODE_TYPES.Identifier) return;\n\n const variable = variableOf(source, node);\n\n if (variable !== null) kinds.set(variable, kind);\n}\n\n/** `const hooks = useModel(M, select)` names the record; `const { save } = useModel(M, select)` names each key. */\nfunction markPattern(source: SourceCode, node: TSESTree.Node, kinds: HookKinds, whole: HookKind, each: HookKind): void {\n if (node.type !== AST_NODE_TYPES.ObjectPattern) {\n mark(source, node, kinds, whole);\n\n return;\n }\n\n for (const property of node.properties) {\n if (property.type === AST_NODE_TYPES.Property) mark(source, property.value, kinds, each);\n }\n}\n\n/** A reassignable name holds whatever was last written to it; only a `const` holds the hook it was opened with. */\nfunction constantCall(\n source: SourceCode,\n declarator: TSESTree.VariableDeclarator,\n): TSESTree.CallExpression | undefined {\n if (declarator.parent.kind !== 'const' || declarator.init === null) return undefined;\n\n const value = localValue(source, declarator.init);\n\n return value.type === AST_NODE_TYPES.CallExpression ? value : undefined;\n}\n\n/** Records what one declaration binds, so a later dependency array is read against names and not against spelling. */\nfunction classifyDeclaration(source: SourceCode, declarator: TSESTree.VariableDeclarator, kinds: HookKinds): void {\n const call = constantCall(source, declarator);\n\n if (call === undefined) return;\n\n const name = apiName(source, call.callee, '@opetope/react');\n\n if (name === 'useCommand') mark(source, declarator.id, kinds, 'hook');\n // D388: `useCommands` is gone; the record a render depends on now comes from `useModel` with a selection.\n // A selection field is a hook only where the code proves it; without a selector the granted model is stable.\n else if (name === 'useModel' && call.arguments.length >= SELECTION_ARITY)\n markPattern(source, declarator.id, kinds, 'selection', 'candidate');\n}\n\nexport type { Access, Binding, HookKinds, Usage };\nexport { bindingAt, classifyDeclaration, STATUS_MEMBERS, usageIn };\n"],"names":["HOOK_MEMBERS","STATUS_MEMBERS","RECORD_KINDS","SELECTION_ARITY","OPAQUE","UNREAD","step","node","parent","AST_NODE_TYPES","accessAt","source","member","over","accessOf","binding","first","within","callback","usageIn","accesses","opaque","identifier","access","provesRun","kindOf","kinds","variable","provedBinding","namedBinding","variableOf","kind","record","keyRead","object","unwrap","keyedBinding","read","bindingAt","element","value","mark","markPattern","whole","each","property","constantCall","declarator","localValue","classifyDeclaration","call","name","apiName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command-hooks.js","sources":["../src/command-hooks.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { apiName, localValue, unwrap, variableOf } from './model-bindings';\nimport type { SourceCode, Variable } from './rule';\n\n/** What a name holds: one hook, a selection whose fields may be hooks, or one such field (D388). */\ntype HookKind = 'candidate' | 'hook' | 'selection';\ntype HookKinds = Map<Variable, HookKind>;\n/**\n * What a dependency element names: one hook (no key, not a record), one key of a record, or the record itself,\n * which is a fresh object on every render.\n */\ntype Binding = {\n readonly key: string | undefined;\n readonly proved: boolean;\n readonly record: boolean;\n readonly variable: Variable;\n};\n/** One read of the binding: the member it ends in, and the text the author wrote to reach it. */\ntype Access = { readonly member: string | undefined; readonly optional: boolean; readonly text: string };\n/** `opaque` is a use no member replaces: the object passed on, destructured, or read past a member we know. */\ntype Usage = { readonly accesses: readonly Access[]; readonly opaque: boolean };\ntype Step = { readonly key: string; readonly node: TSESTree.MemberExpression; readonly optional: boolean };\ntype Occurrence = TSESTree.Identifier | TSESTree.MemberExpression;\n\n/** D543: `dismiss` changes with the outcome, so it is a dependency the rule can offer, as a status is. */\nconst HOOK_MEMBERS = new Set(['dismiss', 'inFlight', 'outcome', 'run']);\nconst STATUS_MEMBERS = ['inFlight', 'outcome'];\nconst RECORD_KINDS = new Set(['selection']);\nconst SELECTION_ARITY = 2;\nconst OPAQUE: Access = { member: undefined, optional: false, text: '' };\n/** A callback this module cannot read is not proof of anything, so its binding takes no fix and no suggestion. */\nconst UNREAD: Usage = { accesses: [], opaque: true };\n\n/** One written step off an occurrence: `.save`, with the `?.` the author may have put in front of it. */\nfunction step(node: Occurrence): Step | undefined {\n const { parent } = node;\n\n if (parent.type !== AST_NODE_TYPES.MemberExpression || parent.object !== node || parent.computed) return undefined;\n\n if (parent.property.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return { key: parent.property.name, node: parent, optional: parent.optional };\n}\n\n/** A step that lands on a member this rule knows; anything else is a use it must not rewrite. */\nfunction accessAt(source: SourceCode, member: Step | undefined, over: boolean): Access {\n if (member === undefined || !HOOK_MEMBERS.has(member.key)) return OPAQUE;\n\n return { member: member.key, optional: over || member.optional, text: source.getText(member.node) };\n}\n\n/** What this occurrence reads on the binding: one step for a hook, two for a record — key, then member. */\nfunction accessOf(source: SourceCode, node: TSESTree.Identifier, binding: Binding): Access | undefined {\n const first = step(node);\n\n if (!binding.record && binding.key === undefined) return accessAt(source, first, false);\n\n if (first === undefined) return OPAQUE;\n\n if (binding.key === undefined) return accessAt(source, step(first.node), first.optional);\n\n return first.key === binding.key ? accessAt(source, step(first.node), first.optional) : undefined;\n}\n\nfunction within(node: TSESTree.Node, callback: TSESTree.Node): boolean {\n return node.range[0] >= callback.range[0] && node.range[1] <= callback.range[1];\n}\n\n/** Every read of the binding inside one callback, and whether any of them takes the object as a whole. */\nfunction usageIn(source: SourceCode, binding: Binding, callback: TSESTree.Node | undefined): Usage {\n if (callback === undefined) return UNREAD;\n\n const accesses: Access[] = [];\n let opaque = false;\n\n for (const { identifier } of binding.variable.references) {\n if (identifier.type !== AST_NODE_TYPES.Identifier || !within(identifier, callback)) continue;\n\n const access = accessOf(source, identifier, binding);\n\n if (access === undefined) continue;\n if (access.member === undefined) opaque = true;\n else accesses.push(access);\n }\n\n return { accesses, opaque };\n}\n\n/**\n * A selected field is a command hook where the code calls or reads `run` on it. A status name alone proves\n * nothing: plain data may carry one, and this package reads shapes rather than types.\n */\nfunction provesRun(source: SourceCode, binding: Binding): boolean {\n for (const { identifier } of binding.variable.references) {\n if (identifier.type !== AST_NODE_TYPES.Identifier) continue;\n\n if (accessOf(source, identifier, binding)?.member === 'run') return true;\n }\n\n return false;\n}\n\nfunction kindOf(kinds: HookKinds, variable: Variable | null): string {\n return variable === null ? '' : (kinds.get(variable) ?? '');\n}\n\n/** A kind that is a claim about a selected field keeps the binding only where the code proves `run` on it. */\nfunction provedBinding(source: SourceCode, binding: Binding): Binding | undefined {\n return !binding.proved || provesRun(source, binding) ? binding : undefined;\n}\n\nfunction namedBinding(source: SourceCode, kinds: HookKinds, node: TSESTree.Identifier): Binding | undefined {\n const variable = variableOf(source, node);\n const kind = kindOf(kinds, variable);\n\n if (variable === null || kind === '') return undefined;\n\n const record = RECORD_KINDS.has(kind);\n\n return provedBinding(source, { key: undefined, proved: kind === 'candidate', record, variable });\n}\n\n/** `hooks.save`: the plain name it is read from and the key it reads, when both of them are written as names. */\nfunction keyRead(node: TSESTree.MemberExpression): { key: string; object: TSESTree.Identifier } | undefined {\n const object = unwrap(node.object);\n\n if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return object.type === AST_NODE_TYPES.Identifier ? { key: node.property.name, object } : undefined;\n}\n\nfunction keyedBinding(source: SourceCode, kinds: HookKinds, node: TSESTree.MemberExpression): Binding | undefined {\n const read = keyRead(node);\n\n if (read === undefined) return undefined;\n\n const variable = variableOf(source, read.object);\n const kind = kindOf(kinds, variable);\n\n if (variable === null || !RECORD_KINDS.has(kind)) return undefined;\n\n return provedBinding(source, { key: read.key, proved: kind === 'selection', record: false, variable });\n}\n\n/** The binding a dependency element names as a whole; a member of one is a member, and this returns nothing. */\nfunction bindingAt(source: SourceCode, kinds: HookKinds, element: TSESTree.Node): Binding | undefined {\n const value = unwrap(element);\n\n if (value.type === AST_NODE_TYPES.Identifier) return namedBinding(source, kinds, value);\n\n return value.type === AST_NODE_TYPES.MemberExpression ? keyedBinding(source, kinds, value) : undefined;\n}\n\nfunction mark(source: SourceCode, node: TSESTree.Node, kinds: HookKinds, kind: HookKind): void {\n if (node.type !== AST_NODE_TYPES.Identifier) return;\n\n const variable = variableOf(source, node);\n\n if (variable !== null) kinds.set(variable, kind);\n}\n\n/** `const hooks = useModel(M, select)` names the record; `const { save } = useModel(M, select)` names each key. */\nfunction markPattern(source: SourceCode, node: TSESTree.Node, kinds: HookKinds, whole: HookKind, each: HookKind): void {\n if (node.type !== AST_NODE_TYPES.ObjectPattern) {\n mark(source, node, kinds, whole);\n\n return;\n }\n\n for (const property of node.properties) {\n if (property.type === AST_NODE_TYPES.Property) mark(source, property.value, kinds, each);\n }\n}\n\n/** A reassignable name holds whatever was last written to it; only a `const` holds the hook it was opened with. */\nfunction constantCall(\n source: SourceCode,\n declarator: TSESTree.VariableDeclarator,\n): TSESTree.CallExpression | undefined {\n if (declarator.parent.kind !== 'const' || declarator.init === null) return undefined;\n\n const value = localValue(source, declarator.init);\n\n return value.type === AST_NODE_TYPES.CallExpression ? value : undefined;\n}\n\n/** Records what one declaration binds, so a later dependency array is read against names and not against spelling. */\nfunction classifyDeclaration(source: SourceCode, declarator: TSESTree.VariableDeclarator, kinds: HookKinds): void {\n const call = constantCall(source, declarator);\n\n if (call === undefined) return;\n\n const name = apiName(source, call.callee, '@opetope/react');\n\n if (name === 'useCommand') mark(source, declarator.id, kinds, 'hook');\n // D388: `useCommands` is gone; the record a render depends on now comes from `useModel` with a selection.\n // A selection field is a hook only where the code proves it; without a selector the granted model is stable.\n else if (name === 'useModel' && call.arguments.length >= SELECTION_ARITY)\n markPattern(source, declarator.id, kinds, 'selection', 'candidate');\n}\n\nexport type { Access, Binding, HookKinds, Usage };\nexport { bindingAt, classifyDeclaration, STATUS_MEMBERS, usageIn };\n"],"names":["HOOK_MEMBERS","STATUS_MEMBERS","RECORD_KINDS","SELECTION_ARITY","OPAQUE","UNREAD","step","node","parent","AST_NODE_TYPES","accessAt","source","member","over","accessOf","binding","first","within","callback","usageIn","accesses","opaque","identifier","access","provesRun","kindOf","kinds","variable","provedBinding","namedBinding","variableOf","kind","record","keyRead","object","unwrap","keyedBinding","read","bindingAt","element","value","mark","markPattern","whole","each","property","constantCall","declarator","localValue","classifyDeclaration","call","name","apiName"],"mappings":"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"}
|
|
@@ -3,9 +3,10 @@ import type { FunctionNode } from './model-bindings.js';
|
|
|
3
3
|
import type { SourceCode, Variable } from './rule.js';
|
|
4
4
|
/**
|
|
5
5
|
* The names one callback reads its execution context through: the parameter itself, and what was taken out of it —
|
|
6
|
-
* renamed in the parameter list, taken apart by a declaration inside the body, or given a second name by a
|
|
7
|
-
* A closure over a member, a member stored
|
|
8
|
-
* and every rule that reads this says so in
|
|
6
|
+
* renamed in the parameter list, taken apart by a declaration inside the body, or given a second name by a declaration
|
|
7
|
+
* that initializes it (`const`, `let` or `var`). A closure over a member, a member stored in a record or assigned
|
|
8
|
+
* after its declaration and a context handed to a helper are not followed, and every rule that reads this says so in
|
|
9
|
+
* its own boundary.
|
|
9
10
|
*/
|
|
10
11
|
type ContextMembers<Key extends string> = {
|
|
11
12
|
readonly bound: Map<Key, Set<Variable>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-members.js","sources":["../src/context-members.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { propertyName, within } from './ast';\nimport type { FunctionNode } from './model-bindings';\nimport { unwrap, variableOf } from './model-bindings';\nimport type { SourceCode, Variable } from './rule';\n\n/**\n * The names one callback reads its execution context through: the parameter itself, and what was taken out of it —\n * renamed in the parameter list, taken apart by a declaration inside the body, or given a second name by a
|
|
1
|
+
{"version":3,"file":"context-members.js","sources":["../src/context-members.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { propertyName, within } from './ast';\nimport type { FunctionNode } from './model-bindings';\nimport { unwrap, variableOf } from './model-bindings';\nimport type { SourceCode, Variable } from './rule';\n\n/**\n * The names one callback reads its execution context through: the parameter itself, and what was taken out of it —\n * renamed in the parameter list, taken apart by a declaration inside the body, or given a second name by a declaration\n * that initializes it (`const`, `let` or `var`). A closure over a member, a member stored in a record or assigned\n * after its declaration and a context handed to a helper are not followed, and every rule that reads this says so in\n * its own boundary.\n */\ntype ContextMembers<Key extends string> = {\n readonly bound: Map<Key, Set<Variable>>;\n readonly parameter: Variable | undefined;\n};\n\nconst NONE: ReadonlySet<Variable> = new Set();\n\nfunction boundOf<Key extends string>(members: ContextMembers<Key>, key: Key): ReadonlySet<Variable> {\n return members.bound.get(key) ?? NONE;\n}\n\nfunction add<Key extends string>(members: ContextMembers<Key>, key: Key, variable: Variable): void {\n const bound = members.bound.get(key);\n\n if (bound === undefined) members.bound.set(key, new Set([variable]));\n else bound.add(variable);\n}\n\nfunction isParameter(source: SourceCode, node: TSESTree.Node, parameter: Variable | undefined): boolean {\n return parameter !== undefined && node.type === AST_NODE_TYPES.Identifier && variableOf(source, node) === parameter;\n}\n\n/** `execution.<key>`, read off the context parameter itself. */\nfunction isContextMember<Key extends string>(\n source: SourceCode,\n node: TSESTree.Node,\n members: ContextMembers<Key>,\n key: Key,\n): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n return (\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === key &&\n isParameter(source, node.object, members.parameter)\n );\n}\n\n/** A name taken from the context under this key, or the key read off the context itself. */\nfunction fromContext<Key extends string>(\n source: SourceCode,\n node: TSESTree.Node,\n members: ContextMembers<Key>,\n key: Key,\n): boolean {\n if (node.type !== AST_NODE_TYPES.Identifier) return isContextMember(source, node, members, key);\n\n const variable = variableOf(source, node);\n\n return variable !== null && boundOf(members, key).has(variable);\n}\n\n/** `({ update, signal: aborting })`: the variables a pattern bound under the keys a rule reads. */\nfunction takeFrom<Key extends string>(\n source: SourceCode,\n pattern: TSESTree.ObjectPattern,\n members: ContextMembers<Key>,\n keys: readonly Key[],\n): void {\n for (const property of pattern.properties) {\n if (property.type !== AST_NODE_TYPES.Property || property.value.type !== AST_NODE_TYPES.Identifier) continue;\n\n const name = propertyName(property);\n const key = keys.find(candidate => candidate === name);\n const variable = key === undefined ? null : variableOf(source, property.value);\n\n if (key !== undefined && variable !== null) add(members, key, variable);\n }\n}\n\n/**\n * `const { update } = execution` takes the context apart one line later, and `const write = update` gives a name the\n * body already holds a second one. Declarations are read in the order they are written, so a name is known before an\n * alias of it.\n */\nfunction takeFromDeclarator<Key extends string>(\n source: SourceCode,\n declarator: TSESTree.VariableDeclarator,\n members: ContextMembers<Key>,\n keys: readonly Key[],\n): void {\n const { id, init } = declarator;\n\n if (init === null) return;\n\n const value = unwrap(init);\n\n if (id.type === AST_NODE_TYPES.ObjectPattern) {\n if (isParameter(source, value, members.parameter)) takeFrom(source, id, members, keys);\n\n return;\n }\n\n const variable = id.type === AST_NODE_TYPES.Identifier ? variableOf(source, id) : null;\n const key = keys.find(candidate => fromContext(source, value, members, candidate));\n\n if (variable !== null && key !== undefined) add(members, key, variable);\n}\n\n/** The context parameter under the name it arrived with, or nothing where the callback took it apart at once. */\nfunction receivedParameter(source: SourceCode, received: TSESTree.Parameter | undefined): Variable | undefined {\n return received?.type === AST_NODE_TYPES.Identifier ? (variableOf(source, received) ?? undefined) : undefined;\n}\n\n/**\n * The context of one callback, whether it arrived whole under a name or taken apart where the callback receives it.\n * The position is part of the contract: a command takes its context second and a reaction run takes it first.\n */\nfunction contextMembers<Key extends string>(\n source: SourceCode,\n fn: FunctionNode,\n position: number,\n keys: readonly Key[],\n declarators: readonly TSESTree.VariableDeclarator[],\n): ContextMembers<Key> {\n const received = fn.params[position];\n const members: ContextMembers<Key> = { bound: new Map(), parameter: receivedParameter(source, received) };\n\n if (received?.type === AST_NODE_TYPES.ObjectPattern) takeFrom(source, received, members, keys);\n\n for (const declarator of declarators) {\n if (within(declarator, fn)) takeFromDeclarator(source, declarator, members, keys);\n }\n\n return members;\n}\n\nexport type { ContextMembers };\nexport { contextMembers, fromContext };\n"],"names":["NONE","boundOf","members","key","add","variable","bound","isParameter","source","node","parameter","AST_NODE_TYPES","variableOf","isContextMember","fromContext","takeFrom","pattern","keys","property","name","propertyName","candidate","takeFromDeclarator","declarator","id","init","value","unwrap","receivedParameter","received","contextMembers","fn","position","declarators","within"],"mappings":"2KAoBA,MAAMA,EAA8B,IAAI,IAExC,SAASC,EAA4BC,EAA8BC,EAAQ,CACzE,OAAOD,EAAQ,MAAM,IAAIC,CAAG,GAAKH,CACnC,CAEA,SAASI,EAAwBF,EAA8BC,EAAUE,EAAkB,CACzF,MAAMC,EAAQJ,EAAQ,MAAM,IAAIC,CAAG,EAE/BG,IAAU,OAAWJ,EAAQ,MAAM,IAAIC,EAAK,IAAI,IAAI,CAACE,CAAQ,CAAC,CAAC,EAC9DC,EAAM,IAAID,CAAQ,CACzB,CAEA,SAASE,EAAYC,EAAoBC,EAAqBC,EAA+B,CAC3F,OAAOA,IAAc,QAAaD,EAAK,OAASE,EAAe,YAAcC,EAAWJ,EAAQC,CAAI,IAAMC,CAC5G,CAGA,SAASG,EACPL,EACAC,EACAP,EACAC,EAAQ,CAER,OAAIM,EAAK,OAASE,EAAe,kBAAoBF,EAAK,SAAiB,GAGzEA,EAAK,SAAS,OAASE,EAAe,YACtCF,EAAK,SAAS,OAASN,GACvBI,EAAYC,EAAQC,EAAK,OAAQP,EAAQ,SAAS,CAEtD,CAGA,SAASY,EACPN,EACAC,EACAP,EACAC,EAAQ,CAER,GAAIM,EAAK,OAASE,EAAe,WAAY,OAAOE,EAAgBL,EAAQC,EAAMP,EAASC,CAAG,EAE9F,MAAME,EAAWO,EAAWJ,EAAQC,CAAI,EAExC,OAAOJ,IAAa,MAAQJ,EAAQC,EAASC,CAAG,EAAE,IAAIE,CAAQ,CAChE,CAGA,SAASU,EACPP,EACAQ,EACAd,EACAe,EAAoB,CAEpB,UAAWC,KAAYF,EAAQ,WAAY,CACzC,GAAIE,EAAS,OAASP,EAAe,UAAYO,EAAS,MAAM,OAASP,EAAe,WAAY,SAEpG,MAAMQ,EAAOC,EAAaF,CAAQ,EAC5Bf,EAAMc,EAAK,KAAKI,GAAaA,IAAcF,CAAI,EAC/Cd,EAAWF,IAAQ,OAAY,KAAOS,EAAWJ,EAAQU,EAAS,KAAK,EAEzEf,IAAQ,QAAaE,IAAa,MAAMD,EAAIF,EAASC,EAAKE,CAAQ,CACxE,CACF,CAOA,SAASiB,EACPd,EACAe,EACArB,EACAe,EAAoB,CAEpB,KAAM,CAAE,GAAAO,EAAI,KAAAC,CAAI,EAAKF,EAErB,GAAIE,IAAS,KAAM,OAEnB,MAAMC,EAAQC,EAAOF,CAAI,EAEzB,GAAID,EAAG,OAASb,EAAe,cAAe,CACxCJ,EAAYC,EAAQkB,EAAOxB,EAAQ,SAAS,GAAGa,EAASP,EAAQgB,EAAItB,EAASe,CAAI,EAErF,MACF,CAEA,MAAMZ,EAAWmB,EAAG,OAASb,EAAe,WAAaC,EAAWJ,EAAQgB,CAAE,EAAI,KAC5ErB,EAAMc,EAAK,KAAKI,GAAaP,EAAYN,EAAQkB,EAAOxB,EAASmB,CAAS,CAAC,EAE7EhB,IAAa,MAAQF,IAAQ,QAAWC,EAAIF,EAASC,EAAKE,CAAQ,CACxE,CAGA,SAASuB,EAAkBpB,EAAoBqB,EAAwC,CACrF,OAAOA,GAAU,OAASlB,EAAe,WAAcC,EAAWJ,EAAQqB,CAAQ,GAAK,OAAa,MACtG,CAMA,SAASC,EACPtB,EACAuB,EACAC,EACAf,EACAgB,EAAmD,CAEnD,MAAMJ,EAAWE,EAAG,OAAOC,CAAQ,EAC7B9B,EAA+B,CAAE,MAAO,IAAI,IAAO,UAAW0B,EAAkBpB,EAAQqB,CAAQ,CAAC,EAEnGA,GAAU,OAASlB,EAAe,eAAeI,EAASP,EAAQqB,EAAU3B,EAASe,CAAI,EAE7F,UAAWM,KAAcU,EACnBC,EAAOX,EAAYQ,CAAE,GAAGT,EAAmBd,EAAQe,EAAYrB,EAASe,CAAI,EAGlF,OAAOf,CACT"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{captureCommandCleanup as i}from"./rules/capture-command-cleanup.js";import{defineFeaturePropertyOrder as s}from"./rules/define-feature-property-order.js";import{idNaming as a}from"./rules/id-naming.js";import{layerPlacement as m}from"./rules/layer-placement.js";import{noBehaviorInIntegration as l}from"./rules/no-behavior-in-integration.js";import{noCommandInDeps as d}from"./rules/no-command-in-deps.js";import{noDeadAbortCheck as c}from"./rules/no-dead-abort-check.js";import{noEventWithoutEmit as u}from"./rules/no-event-without-emit.js";import{noInlineRequiresModels as f}from"./rules/no-inline-requires-models.js";import{noInternalImports as b}from"./rules/no-internal-imports.js";import{noRedundantConstTuple as g}from"./rules/no-redundant-const-tuple.js";import{noRetiredVocabulary as h}from"./rules/no-retired-vocabulary.js";import{noSingleCommandLane as v}from"./rules/no-single-command-lane.js";import{noSnapshotInUpdate as w}from"./rules/no-snapshot-in-update.js";import{noSnapshotReadInRender as y}from"./rules/no-snapshot-read-in-render.js";import{noSubscribeOutsideModels as S}from"./rules/no-subscribe-outside-models.js";import{noWriteAfterSourceWrite as q}from"./rules/no-write-after-source-write.js";import{preferDerive as I}from"./rules/prefer-derive.js";import{preferEffectCurrent as R}from"./rules/prefer-effect-current.js";import{preferLazyBodyRecord as E}from"./rules/prefer-lazy-body-record.js";import{preferModelSelection as C}from"./rules/prefer-model-selection.js";import{preferRead as L}from"./rules/prefer-read.js";import{preferWhenOverEnabled as _}from"./rules/prefer-when-over-enabled.js";import{requireDeclaredModels as j}from"./rules/require-declared-models.js";import{requireLiteralId as x}from"./rules/require-literal-id.js";import{requireStaticFeature as M}from"./rules/require-static-feature.js";import{scopeBuilderReleasesSubscriptions as O}from"./rules/scope-builder-releases-subscriptions.js";import{enabledPredicate as P}from"./rules/enabled-predicate.js";const D=["integration","models","ui"],F=["**/*.ts","**/*.tsx"],n=["**/__tests__/**","**/*.spec.ts","**/*.spec.tsx"],W={meta:{name:"@opetope/lint",version:"0.
|
|
1
|
+
import{captureCommandCleanup as i}from"./rules/capture-command-cleanup.js";import{defineFeaturePropertyOrder as s}from"./rules/define-feature-property-order.js";import{idNaming as a}from"./rules/id-naming.js";import{layerPlacement as m}from"./rules/layer-placement.js";import{noBehaviorInIntegration as l}from"./rules/no-behavior-in-integration.js";import{noCommandInDeps as d}from"./rules/no-command-in-deps.js";import{noDeadAbortCheck as c}from"./rules/no-dead-abort-check.js";import{noEventWithoutEmit as u}from"./rules/no-event-without-emit.js";import{noInlineRequiresModels as f}from"./rules/no-inline-requires-models.js";import{noInternalImports as b}from"./rules/no-internal-imports.js";import{noRedundantConstTuple as g}from"./rules/no-redundant-const-tuple.js";import{noRetiredVocabulary as h}from"./rules/no-retired-vocabulary.js";import{noSingleCommandLane as v}from"./rules/no-single-command-lane.js";import{noSnapshotInUpdate as w}from"./rules/no-snapshot-in-update.js";import{noSnapshotReadInRender as y}from"./rules/no-snapshot-read-in-render.js";import{noSubscribeOutsideModels as S}from"./rules/no-subscribe-outside-models.js";import{noWriteAfterSourceWrite as q}from"./rules/no-write-after-source-write.js";import{preferDerive as I}from"./rules/prefer-derive.js";import{preferEffectCurrent as R}from"./rules/prefer-effect-current.js";import{preferLazyBodyRecord as E}from"./rules/prefer-lazy-body-record.js";import{preferModelSelection as C}from"./rules/prefer-model-selection.js";import{preferRead as L}from"./rules/prefer-read.js";import{preferWhenOverEnabled as _}from"./rules/prefer-when-over-enabled.js";import{requireDeclaredModels as j}from"./rules/require-declared-models.js";import{requireLiteralId as x}from"./rules/require-literal-id.js";import{requireStaticFeature as M}from"./rules/require-static-feature.js";import{scopeBuilderReleasesSubscriptions as O}from"./rules/scope-builder-releases-subscriptions.js";import{enabledPredicate as P}from"./rules/enabled-predicate.js";const D=["integration","models","ui"],F=["**/*.ts","**/*.tsx"],n=["**/__tests__/**","**/*.spec.ts","**/*.spec.tsx"],W={meta:{name:"@opetope/lint",version:"0.16.0"},rules:{"capture-command-cleanup":i,"define-feature-property-order":s,"id-naming":a,"layer-placement":m,"no-behavior-in-integration":l,"no-command-in-deps":d,"no-dead-abort-check":c,"no-event-without-emit":u,"no-inline-requires-models":f,"no-internal-imports":b,"no-redundant-const-tuple":g,"no-retired-vocabulary":h,"no-single-command-lane":v,"no-snapshot-in-update":w,"no-snapshot-read-in-render":y,"no-subscribe-outside-models":S,"no-write-after-source-write":q,"prefer-derive":I,"prefer-effect-current":R,"prefer-lazy-body-record":E,"prefer-model-selection":C,"prefer-read":L,"prefer-when-over-enabled":_,"require-declared-models":j,"require-literal-id":x,"require-static-feature":M,"scope-builder-releases-subscriptions":O,"enabled-predicate":P}},r=W,k={name:"opetope/recommended",plugins:{opetope:r},rules:{"opetope/capture-command-cleanup":"error","opetope/define-feature-property-order":"error","opetope/id-naming":"error","opetope/layer-placement":"off","opetope/no-behavior-in-integration":"off","opetope/no-command-in-deps":"error","opetope/no-dead-abort-check":"warn","opetope/no-event-without-emit":"warn","opetope/no-inline-requires-models":"warn","opetope/no-internal-imports":"error","opetope/no-redundant-const-tuple":"error","opetope/no-retired-vocabulary":"error","opetope/no-single-command-lane":"warn","opetope/no-snapshot-in-update":"error","opetope/no-snapshot-read-in-render":"error","opetope/no-subscribe-outside-models":"off","opetope/no-write-after-source-write":"error","opetope/prefer-derive":"warn","opetope/prefer-effect-current":"error","opetope/prefer-lazy-body-record":"warn","opetope/prefer-model-selection":"off","opetope/prefer-read":"warn","opetope/prefer-when-over-enabled":"warn","opetope/require-declared-models":"error","opetope/require-literal-id":"error","opetope/require-static-feature":"warn","opetope/scope-builder-releases-subscriptions":"error","opetope/enabled-predicate":"error"}},z=e=>D.flatMap(o=>{const t=e[o]??[];return t.length===0?[]:[{files:[...t],name:`opetope/layers-${o}`,plugins:{opetope:r},rules:{"opetope/layer-placement":["error",{layer:o}]}}]}),A=({models:e=[],tests:o=n})=>e.length===0?[]:[{files:F,name:"opetope/layers-subscriptions",plugins:{opetope:r},rules:{"opetope/no-subscribe-outside-models":"error","opetope/scope-builder-releases-subscriptions":"error"}},{files:[...e,...o],name:"opetope/layers-subscriptions-allowed",rules:{"opetope/no-subscribe-outside-models":"off"}}],B=({integration:e=[],tests:o=n})=>e.length===0?[]:[{files:[...e],...o.length===0?{}:{ignores:[...o]},name:"opetope/layers-integration-behavior",plugins:{opetope:r},rules:{"opetope/no-behavior-in-integration":"warn"}}],T=e=>[...z(e),...B(e),...A(e)],U=({allow:e=[],files:o=["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"]}={})=>[{files:[...o],name:"opetope/internal-imports",plugins:{opetope:r},rules:{"opetope/no-internal-imports":"error"}},...e.length===0?[]:[{files:[...e],name:"opetope/internal-imports-allowed",rules:{"opetope/no-internal-imports":"off"}}]],N=Object.assign(r,{configs:{internalImports:U,layers:T,recommended:k}}),p=N;export{p as default,p as opetopeLint};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\n\nimport { captureCommandCleanup } from './rules/capture-command-cleanup';\nimport { defineFeaturePropertyOrder } from './rules/define-feature-property-order';\nimport { idNaming } from './rules/id-naming';\nimport { layerPlacement } from './rules/layer-placement';\nimport { noBehaviorInIntegration } from './rules/no-behavior-in-integration';\nimport { noCommandInDeps } from './rules/no-command-in-deps';\nimport { noDeadAbortCheck } from './rules/no-dead-abort-check';\nimport { noEventWithoutEmit } from './rules/no-event-without-emit';\nimport { noInlineRequiresModels } from './rules/no-inline-requires-models';\nimport { noInternalImports } from './rules/no-internal-imports';\nimport { noRedundantConstTuple } from './rules/no-redundant-const-tuple';\nimport { noRetiredVocabulary } from './rules/no-retired-vocabulary';\nimport { noSingleCommandLane } from './rules/no-single-command-lane';\nimport { noSnapshotInUpdate } from './rules/no-snapshot-in-update';\nimport { noSnapshotReadInRender } from './rules/no-snapshot-read-in-render';\nimport { noSubscribeOutsideModels } from './rules/no-subscribe-outside-models';\nimport { noWriteAfterSourceWrite } from './rules/no-write-after-source-write';\nimport { preferDerive } from './rules/prefer-derive';\nimport { preferEffectCurrent } from './rules/prefer-effect-current';\nimport { preferLazyBodyRecord } from './rules/prefer-lazy-body-record';\nimport { preferModelSelection } from './rules/prefer-model-selection';\nimport { preferRead } from './rules/prefer-read';\nimport { preferWhenOverEnabled } from './rules/prefer-when-over-enabled';\nimport { requireDeclaredModels } from './rules/require-declared-models';\nimport { requireLiteralId } from './rules/require-literal-id';\nimport { requireStaticFeature } from './rules/require-static-feature';\nimport { scopeBuilderReleasesSubscriptions } from './rules/scope-builder-releases-subscriptions';\nimport { enabledPredicate } from './rules/enabled-predicate';\n\n/**\n * The layers a host names for itself, as globs of the files that hold each one, with the tests that are allowed\n * to reach past a layer. `tests` defaults to the usual two shapes of a test path.\n */\ntype LayerOptions = {\n /**\n * Globs of the layer that composes features: `layer-placement` holds there, and `no-behavior-in-integration` warns\n * there, except in the files `tests` names. Omitted, the project has no such layer.\n */\n readonly integration?: readonly string[];\n /**\n * Globs of the model layer, where `layer-placement` holds. Naming them turns on `no-subscribe-outside-models` for\n * every source file except this layer and `tests`, and `scope-builder-releases-subscriptions` for every source file.\n */\n readonly models?: readonly string[];\n /**\n * The tests that may subscribe outside the model layer and declare behavior inside the integration layer; an empty\n * list leaves no exception, and `layer-placement` never steps aside for them. Omitted, every `__tests__` directory\n * and every `.spec.ts` and `.spec.tsx` file.\n */\n readonly tests?: readonly string[];\n /** Globs of the UI layer, where `layer-placement` holds. Omitted, the project has no such layer. */\n readonly ui?: readonly string[];\n};\n\ntype InternalImportOptions = {\n /**\n * Trusted library implementation and host integration files, where this rule alone is switched off; place the\n * helper's blocks after `recommended`, which re-enables it.\n * @default []\n */\n readonly allow?: readonly string[];\n /**\n * The files the rule covers. Omitted, every JavaScript and TypeScript file: `js`, `jsx`, `mjs`, `cjs`, `ts`, `tsx`,\n * `mts` and `cts`.\n */\n readonly files?: readonly string[];\n};\n\nconst LAYERS = ['integration', 'models', 'ui'] as const;\nconst SOURCE_FILES = ['**/*.ts', '**/*.tsx'];\nconst TEST_FILES = ['**/__tests__/**', '**/*.spec.ts', '**/*.spec.tsx'];\n\nconst plugin = {\n meta: { name: '@opetope/lint', version: '0.15.0' },\n rules: {\n 'capture-command-cleanup': captureCommandCleanup,\n 'define-feature-property-order': defineFeaturePropertyOrder,\n 'id-naming': idNaming,\n 'layer-placement': layerPlacement,\n 'no-behavior-in-integration': noBehaviorInIntegration,\n 'no-command-in-deps': noCommandInDeps,\n 'no-dead-abort-check': noDeadAbortCheck,\n 'no-event-without-emit': noEventWithoutEmit,\n 'no-inline-requires-models': noInlineRequiresModels,\n 'no-internal-imports': noInternalImports,\n 'no-redundant-const-tuple': noRedundantConstTuple,\n 'no-retired-vocabulary': noRetiredVocabulary,\n 'no-single-command-lane': noSingleCommandLane,\n 'no-snapshot-in-update': noSnapshotInUpdate,\n 'no-snapshot-read-in-render': noSnapshotReadInRender,\n 'no-subscribe-outside-models': noSubscribeOutsideModels,\n 'no-write-after-source-write': noWriteAfterSourceWrite,\n 'prefer-derive': preferDerive,\n 'prefer-effect-current': preferEffectCurrent,\n 'prefer-lazy-body-record': preferLazyBodyRecord,\n 'prefer-model-selection': preferModelSelection,\n 'prefer-read': preferRead,\n 'prefer-when-over-enabled': preferWhenOverEnabled,\n 'require-declared-models': requireDeclaredModels,\n 'require-literal-id': requireLiteralId,\n 'require-static-feature': requireStaticFeature,\n 'scope-builder-releases-subscriptions': scopeBuilderReleasesSubscriptions,\n 'enabled-predicate': enabledPredicate,\n },\n};\n\n/**\n * The laws every Opetope author writes against, with no assumption about where a host keeps its files. A rule that\n * needs such an assumption is off here and arrives through `layers`. The namespace is `opetope`, so a rule reads\n * `opetope/enabled-predicate` wherever a host names it.\n */\n// Rules use the parser's typed AST internally. The public bridge exposes the\n// supported ESLint rule API; package smoke tests execute it under both linters.\ntype NativeRule = NonNullable<ESLint.Plugin['rules']>[string];\ntype NativeRules = {\n [Name in keyof typeof plugin.rules]: Omit<(typeof plugin.rules)[Name], 'create'> & NativeRule;\n};\nconst nativePlugin = plugin as unknown as { meta: typeof plugin.meta; rules: NativeRules };\n\nconst recommended = {\n name: 'opetope/recommended',\n plugins: { opetope: nativePlugin },\n rules: {\n 'opetope/capture-command-cleanup': 'error',\n 'opetope/define-feature-property-order': 'error',\n 'opetope/id-naming': 'error',\n 'opetope/layer-placement': 'off',\n 'opetope/no-behavior-in-integration': 'off',\n 'opetope/no-command-in-deps': 'error',\n 'opetope/no-dead-abort-check': 'warn',\n 'opetope/no-event-without-emit': 'warn',\n 'opetope/no-inline-requires-models': 'warn',\n 'opetope/no-internal-imports': 'error',\n 'opetope/no-redundant-const-tuple': 'error',\n 'opetope/no-retired-vocabulary': 'error',\n 'opetope/no-single-command-lane': 'warn',\n 'opetope/no-snapshot-in-update': 'error',\n 'opetope/no-snapshot-read-in-render': 'error',\n 'opetope/no-subscribe-outside-models': 'off',\n 'opetope/no-write-after-source-write': 'error',\n 'opetope/prefer-derive': 'warn',\n 'opetope/prefer-effect-current': 'error',\n 'opetope/prefer-lazy-body-record': 'warn',\n 'opetope/prefer-model-selection': 'off',\n 'opetope/prefer-read': 'warn',\n 'opetope/prefer-when-over-enabled': 'warn',\n 'opetope/require-declared-models': 'error',\n 'opetope/require-literal-id': 'error',\n 'opetope/require-static-feature': 'warn',\n 'opetope/scope-builder-releases-subscriptions': 'error',\n 'opetope/enabled-predicate': 'error',\n },\n} satisfies Linter.Config;\n\n/**\n * Placement is a convention of the project that names its layers, not a law of the framework: a layer without a\n * glob is a layer this host does not have, and it produces no configuration.\n */\nconst placement = (options: LayerOptions): readonly Linter.Config[] =>\n LAYERS.flatMap(layer => {\n const files = options[layer] ?? [];\n\n if (files.length === 0) return [];\n\n return [\n {\n files: [...files],\n name: `opetope/layers-${layer}`,\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/layer-placement': ['error', { layer }] },\n } satisfies Linter.Config,\n ];\n });\n\n/**\n * A subscription is written where the state lives, so this pair arrives only with the model layer that owns it. The\n * restriction covers every source file and steps aside in that layer and in tests: a file no layer claims is\n * covered rather than forgotten. The body of a model scope builder is the exception to the exception: it returns\n * fields, not a disposer, so `scope-builder-releases-subscriptions` stays on in the model layer too (D470).\n */\nconst subscriptions = ({ models = [], tests = TEST_FILES }: LayerOptions): readonly Linter.Config[] =>\n models.length === 0\n ? []\n : [\n {\n files: SOURCE_FILES,\n name: 'opetope/layers-subscriptions',\n plugins: { opetope: nativePlugin },\n rules: {\n 'opetope/no-subscribe-outside-models': 'error',\n 'opetope/scope-builder-releases-subscriptions': 'error',\n },\n },\n {\n files: [...models, ...tests],\n name: 'opetope/layers-subscriptions-allowed',\n rules: { 'opetope/no-subscribe-outside-models': 'off' },\n },\n ];\n\n/**\n * The integration layer composes and declares no model behaviour, which is the same kind of convention, so it arrives\n * here too, as a warning. A test builds its fixtures where it stands, so the block leaves out the integration files\n * that are tests, and only those: a host that turns the rule on for its tests elsewhere keeps it there (D494).\n */\nconst behaviour = ({ integration = [], tests = TEST_FILES }: LayerOptions): readonly Linter.Config[] =>\n integration.length === 0\n ? []\n : [\n {\n files: [...integration],\n ...(tests.length === 0 ? {} : { ignores: [...tests] }),\n name: 'opetope/layers-integration-behavior',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-behavior-in-integration': 'warn' },\n },\n ];\n\nconst layers = (options: LayerOptions): readonly Linter.Config[] => [\n ...placement(options),\n ...behaviour(options),\n ...subscriptions(options),\n];\n\n/**\n * Only library implementation and explicitly named host integration can cross the internal ABI. A dedicated rule\n * leaves the project's `no-restricted-imports` untouched and also covers literal dynamic imports in JS tests.\n * Allowed files explicitly override `recommended`, which enables this rule as well.\n */\nconst internalImports = ({\n allow = [],\n files = ['**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'],\n}: InternalImportOptions = {}): readonly Linter.Config[] => [\n {\n files: [...files],\n name: 'opetope/internal-imports',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-internal-imports': 'error' },\n },\n ...(allow.length === 0\n ? []\n : [\n {\n files: [...allow],\n name: 'opetope/internal-imports-allowed',\n rules: { 'opetope/no-internal-imports': 'off' as const },\n },\n ]),\n];\n\nconst configuredPlugin = Object.assign(nativePlugin, {\n configs: { internalImports, layers, recommended },\n});\n\n// ESLint consumes the rule map. Config factories are additional plugin helpers;\n// expose both contracts without requiring casts at native flat-config call sites.\n// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- Required by native ESLint config typetests.\nconst opetopeLint = configuredPlugin as unknown as typeof configuredPlugin & Pick<ESLint.Plugin, 'configs'>;\n\n/*\n * Two shapes of one object. The default is what a tool reads when it loads a plugin by path — Oxlint's `jsPlugins`\n * takes `(await import(path)).default` — and what an ESLint flat config imports by convention; the named export is\n * for a config that prefers to say which binding it takes.\n */\n// oxlint-disable-next-line import/no-default-export\n// eslint-disable-next-line import-x/no-default-export\nexport default opetopeLint;\nexport { opetopeLint };\n"],"names":["LAYERS","SOURCE_FILES","TEST_FILES","plugin","captureCommandCleanup","defineFeaturePropertyOrder","idNaming","layerPlacement","noBehaviorInIntegration","noCommandInDeps","noDeadAbortCheck","noEventWithoutEmit","noInlineRequiresModels","noInternalImports","noRedundantConstTuple","noRetiredVocabulary","noSingleCommandLane","noSnapshotInUpdate","noSnapshotReadInRender","noSubscribeOutsideModels","noWriteAfterSourceWrite","preferDerive","preferEffectCurrent","preferLazyBodyRecord","preferModelSelection","preferRead","preferWhenOverEnabled","requireDeclaredModels","requireLiteralId","requireStaticFeature","scopeBuilderReleasesSubscriptions","enabledPredicate","nativePlugin","recommended","placement","options","layer","files","subscriptions","models","tests","behaviour","integration","layers","internalImports","allow","configuredPlugin","opetopeLint"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\n\nimport { captureCommandCleanup } from './rules/capture-command-cleanup';\nimport { defineFeaturePropertyOrder } from './rules/define-feature-property-order';\nimport { idNaming } from './rules/id-naming';\nimport { layerPlacement } from './rules/layer-placement';\nimport { noBehaviorInIntegration } from './rules/no-behavior-in-integration';\nimport { noCommandInDeps } from './rules/no-command-in-deps';\nimport { noDeadAbortCheck } from './rules/no-dead-abort-check';\nimport { noEventWithoutEmit } from './rules/no-event-without-emit';\nimport { noInlineRequiresModels } from './rules/no-inline-requires-models';\nimport { noInternalImports } from './rules/no-internal-imports';\nimport { noRedundantConstTuple } from './rules/no-redundant-const-tuple';\nimport { noRetiredVocabulary } from './rules/no-retired-vocabulary';\nimport { noSingleCommandLane } from './rules/no-single-command-lane';\nimport { noSnapshotInUpdate } from './rules/no-snapshot-in-update';\nimport { noSnapshotReadInRender } from './rules/no-snapshot-read-in-render';\nimport { noSubscribeOutsideModels } from './rules/no-subscribe-outside-models';\nimport { noWriteAfterSourceWrite } from './rules/no-write-after-source-write';\nimport { preferDerive } from './rules/prefer-derive';\nimport { preferEffectCurrent } from './rules/prefer-effect-current';\nimport { preferLazyBodyRecord } from './rules/prefer-lazy-body-record';\nimport { preferModelSelection } from './rules/prefer-model-selection';\nimport { preferRead } from './rules/prefer-read';\nimport { preferWhenOverEnabled } from './rules/prefer-when-over-enabled';\nimport { requireDeclaredModels } from './rules/require-declared-models';\nimport { requireLiteralId } from './rules/require-literal-id';\nimport { requireStaticFeature } from './rules/require-static-feature';\nimport { scopeBuilderReleasesSubscriptions } from './rules/scope-builder-releases-subscriptions';\nimport { enabledPredicate } from './rules/enabled-predicate';\n\n/**\n * The layers a host names for itself, as globs of the files that hold each one, with the tests that are allowed\n * to reach past a layer. `tests` defaults to the usual two shapes of a test path.\n */\ntype LayerOptions = {\n /**\n * Globs of the layer that composes features: `layer-placement` holds there, and `no-behavior-in-integration` warns\n * there, except in the files `tests` names. Omitted, the project has no such layer.\n */\n readonly integration?: readonly string[];\n /**\n * Globs of the model layer, where `layer-placement` holds. Naming them turns on `no-subscribe-outside-models` for\n * every source file except this layer and `tests`, and `scope-builder-releases-subscriptions` for every source file.\n */\n readonly models?: readonly string[];\n /**\n * The tests that may subscribe outside the model layer and declare behavior inside the integration layer; an empty\n * list leaves no exception, and `layer-placement` never steps aside for them. Omitted, every `__tests__` directory\n * and every `.spec.ts` and `.spec.tsx` file.\n */\n readonly tests?: readonly string[];\n /** Globs of the UI layer, where `layer-placement` holds. Omitted, the project has no such layer. */\n readonly ui?: readonly string[];\n};\n\ntype InternalImportOptions = {\n /**\n * Trusted library implementation and host integration files, where this rule alone is switched off; place the\n * helper's blocks after `recommended`, which re-enables it.\n * @default []\n */\n readonly allow?: readonly string[];\n /**\n * The files the rule covers. Omitted, every JavaScript and TypeScript file: `js`, `jsx`, `mjs`, `cjs`, `ts`, `tsx`,\n * `mts` and `cts`.\n */\n readonly files?: readonly string[];\n};\n\nconst LAYERS = ['integration', 'models', 'ui'] as const;\nconst SOURCE_FILES = ['**/*.ts', '**/*.tsx'];\nconst TEST_FILES = ['**/__tests__/**', '**/*.spec.ts', '**/*.spec.tsx'];\n\nconst plugin = {\n meta: { name: '@opetope/lint', version: '0.16.0' },\n rules: {\n 'capture-command-cleanup': captureCommandCleanup,\n 'define-feature-property-order': defineFeaturePropertyOrder,\n 'id-naming': idNaming,\n 'layer-placement': layerPlacement,\n 'no-behavior-in-integration': noBehaviorInIntegration,\n 'no-command-in-deps': noCommandInDeps,\n 'no-dead-abort-check': noDeadAbortCheck,\n 'no-event-without-emit': noEventWithoutEmit,\n 'no-inline-requires-models': noInlineRequiresModels,\n 'no-internal-imports': noInternalImports,\n 'no-redundant-const-tuple': noRedundantConstTuple,\n 'no-retired-vocabulary': noRetiredVocabulary,\n 'no-single-command-lane': noSingleCommandLane,\n 'no-snapshot-in-update': noSnapshotInUpdate,\n 'no-snapshot-read-in-render': noSnapshotReadInRender,\n 'no-subscribe-outside-models': noSubscribeOutsideModels,\n 'no-write-after-source-write': noWriteAfterSourceWrite,\n 'prefer-derive': preferDerive,\n 'prefer-effect-current': preferEffectCurrent,\n 'prefer-lazy-body-record': preferLazyBodyRecord,\n 'prefer-model-selection': preferModelSelection,\n 'prefer-read': preferRead,\n 'prefer-when-over-enabled': preferWhenOverEnabled,\n 'require-declared-models': requireDeclaredModels,\n 'require-literal-id': requireLiteralId,\n 'require-static-feature': requireStaticFeature,\n 'scope-builder-releases-subscriptions': scopeBuilderReleasesSubscriptions,\n 'enabled-predicate': enabledPredicate,\n },\n};\n\n/**\n * The laws every Opetope author writes against, with no assumption about where a host keeps its files. A rule that\n * needs such an assumption is off here and arrives through `layers`. The namespace is `opetope`, so a rule reads\n * `opetope/enabled-predicate` wherever a host names it.\n */\n// Rules use the parser's typed AST internally. The public bridge exposes the\n// supported ESLint rule API; package smoke tests execute it under both linters.\ntype NativeRule = NonNullable<ESLint.Plugin['rules']>[string];\ntype NativeRules = {\n [Name in keyof typeof plugin.rules]: Omit<(typeof plugin.rules)[Name], 'create'> & NativeRule;\n};\nconst nativePlugin = plugin as unknown as { meta: typeof plugin.meta; rules: NativeRules };\n\nconst recommended = {\n name: 'opetope/recommended',\n plugins: { opetope: nativePlugin },\n rules: {\n 'opetope/capture-command-cleanup': 'error',\n 'opetope/define-feature-property-order': 'error',\n 'opetope/id-naming': 'error',\n 'opetope/layer-placement': 'off',\n 'opetope/no-behavior-in-integration': 'off',\n 'opetope/no-command-in-deps': 'error',\n 'opetope/no-dead-abort-check': 'warn',\n 'opetope/no-event-without-emit': 'warn',\n 'opetope/no-inline-requires-models': 'warn',\n 'opetope/no-internal-imports': 'error',\n 'opetope/no-redundant-const-tuple': 'error',\n 'opetope/no-retired-vocabulary': 'error',\n 'opetope/no-single-command-lane': 'warn',\n 'opetope/no-snapshot-in-update': 'error',\n 'opetope/no-snapshot-read-in-render': 'error',\n 'opetope/no-subscribe-outside-models': 'off',\n 'opetope/no-write-after-source-write': 'error',\n 'opetope/prefer-derive': 'warn',\n 'opetope/prefer-effect-current': 'error',\n 'opetope/prefer-lazy-body-record': 'warn',\n 'opetope/prefer-model-selection': 'off',\n 'opetope/prefer-read': 'warn',\n 'opetope/prefer-when-over-enabled': 'warn',\n 'opetope/require-declared-models': 'error',\n 'opetope/require-literal-id': 'error',\n 'opetope/require-static-feature': 'warn',\n 'opetope/scope-builder-releases-subscriptions': 'error',\n 'opetope/enabled-predicate': 'error',\n },\n} satisfies Linter.Config;\n\n/**\n * Placement is a convention of the project that names its layers, not a law of the framework: a layer without a\n * glob is a layer this host does not have, and it produces no configuration.\n */\nconst placement = (options: LayerOptions): readonly Linter.Config[] =>\n LAYERS.flatMap(layer => {\n const files = options[layer] ?? [];\n\n if (files.length === 0) return [];\n\n return [\n {\n files: [...files],\n name: `opetope/layers-${layer}`,\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/layer-placement': ['error', { layer }] },\n } satisfies Linter.Config,\n ];\n });\n\n/**\n * A subscription is written where the state lives, so this pair arrives only with the model layer that owns it. The\n * restriction covers every source file and steps aside in that layer and in tests: a file no layer claims is\n * covered rather than forgotten. The body of a model scope builder is the exception to the exception: it returns\n * fields, not a disposer, so `scope-builder-releases-subscriptions` stays on in the model layer too (D470).\n */\nconst subscriptions = ({ models = [], tests = TEST_FILES }: LayerOptions): readonly Linter.Config[] =>\n models.length === 0\n ? []\n : [\n {\n files: SOURCE_FILES,\n name: 'opetope/layers-subscriptions',\n plugins: { opetope: nativePlugin },\n rules: {\n 'opetope/no-subscribe-outside-models': 'error',\n 'opetope/scope-builder-releases-subscriptions': 'error',\n },\n },\n {\n files: [...models, ...tests],\n name: 'opetope/layers-subscriptions-allowed',\n rules: { 'opetope/no-subscribe-outside-models': 'off' },\n },\n ];\n\n/**\n * The integration layer composes and declares no model behaviour, which is the same kind of convention, so it arrives\n * here too, as a warning. A test builds its fixtures where it stands, so the block leaves out the integration files\n * that are tests, and only those: a host that turns the rule on for its tests elsewhere keeps it there (D494).\n */\nconst behaviour = ({ integration = [], tests = TEST_FILES }: LayerOptions): readonly Linter.Config[] =>\n integration.length === 0\n ? []\n : [\n {\n files: [...integration],\n ...(tests.length === 0 ? {} : { ignores: [...tests] }),\n name: 'opetope/layers-integration-behavior',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-behavior-in-integration': 'warn' },\n },\n ];\n\nconst layers = (options: LayerOptions): readonly Linter.Config[] => [\n ...placement(options),\n ...behaviour(options),\n ...subscriptions(options),\n];\n\n/**\n * Only library implementation and explicitly named host integration can cross the internal ABI. A dedicated rule\n * leaves the project's `no-restricted-imports` untouched and also covers literal dynamic imports in JS tests.\n * Allowed files explicitly override `recommended`, which enables this rule as well.\n */\nconst internalImports = ({\n allow = [],\n files = ['**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'],\n}: InternalImportOptions = {}): readonly Linter.Config[] => [\n {\n files: [...files],\n name: 'opetope/internal-imports',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-internal-imports': 'error' },\n },\n ...(allow.length === 0\n ? []\n : [\n {\n files: [...allow],\n name: 'opetope/internal-imports-allowed',\n rules: { 'opetope/no-internal-imports': 'off' as const },\n },\n ]),\n];\n\nconst configuredPlugin = Object.assign(nativePlugin, {\n configs: { internalImports, layers, recommended },\n});\n\n// ESLint consumes the rule map. Config factories are additional plugin helpers;\n// expose both contracts without requiring casts at native flat-config call sites.\n// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- Required by native ESLint config typetests.\nconst opetopeLint = configuredPlugin as unknown as typeof configuredPlugin & Pick<ESLint.Plugin, 'configs'>;\n\n/*\n * Two shapes of one object. The default is what a tool reads when it loads a plugin by path — Oxlint's `jsPlugins`\n * takes `(await import(path)).default` — and what an ESLint flat config imports by convention; the named export is\n * for a config that prefers to say which binding it takes.\n */\n// oxlint-disable-next-line import/no-default-export\n// eslint-disable-next-line import-x/no-default-export\nexport default opetopeLint;\nexport { opetopeLint };\n"],"names":["LAYERS","SOURCE_FILES","TEST_FILES","plugin","captureCommandCleanup","defineFeaturePropertyOrder","idNaming","layerPlacement","noBehaviorInIntegration","noCommandInDeps","noDeadAbortCheck","noEventWithoutEmit","noInlineRequiresModels","noInternalImports","noRedundantConstTuple","noRetiredVocabulary","noSingleCommandLane","noSnapshotInUpdate","noSnapshotReadInRender","noSubscribeOutsideModels","noWriteAfterSourceWrite","preferDerive","preferEffectCurrent","preferLazyBodyRecord","preferModelSelection","preferRead","preferWhenOverEnabled","requireDeclaredModels","requireLiteralId","requireStaticFeature","scopeBuilderReleasesSubscriptions","enabledPredicate","nativePlugin","recommended","placement","options","layer","files","subscriptions","models","tests","behaviour","integration","layers","internalImports","allow","configuredPlugin","opetopeLint"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The words `no-retired-vocabulary` names, and what it says about each: the migration a retired word asks for. */
|
|
2
2
|
import type { ResourceMessageId } from './resource-vocabulary.js';
|
|
3
|
-
type MessageIds = ResourceMessageId | 'commandLane' | 'commandLatest' | 'commandInputArgs' | 'commandName' | 'commandOnce' | 'commandOutcomeStatus' | 'commandPolicy' | 'commandQueueBy' | 'conditionFrom' | 'contextCall' | 'contextCalls' | 'contributionWhen' | 'deliveryKey' | 'deliveryKind' | 'deliveryReject' | 'effectOnDispose' | 'effectSkipInitial' | 'effectWhen' | 'entryMoved' | 'entryRenamed' | 'eventBackpressure' | 'externalPublish' | 'fromExternal' | 'featureOwnMember' | 'linkSourcePending' | 'reactFeatureDemand' | 'reactUseSelector' | 'navigatorPolicy' | 'paginateNextCursor' | 'resourceActivity' | 'resourceDisposer' | 'resourceInvalidate' | 'resourcePagination' | 'runtimeInternalEntry' | 'scopeKeyed' | 'scopeWhileWhen' | 'stateActivity' | 'surfaceView' | 'useCommands';
|
|
3
|
+
type MessageIds = ResourceMessageId | 'commandLane' | 'commandLatest' | 'commandInputArgs' | 'commandName' | 'commandOnce' | 'commandOutcomeStatus' | 'commandPolicy' | 'commandQueueBy' | 'conditionFrom' | 'contextCall' | 'contextCalls' | 'contributionWhen' | 'deliveryKey' | 'deliveryKind' | 'deliveryReject' | 'effectOnDispose' | 'effectSkipInitial' | 'effectWhen' | 'entryMoved' | 'entryRenamed' | 'eventBackpressure' | 'externalPublish' | 'fromExternal' | 'featureBodyOf' | 'featureOwnMember' | 'linkSourcePending' | 'reactFeatureDemand' | 'reactUseSelector' | 'navigatorPolicy' | 'paginateNextCursor' | 'resourceActivity' | 'resourceDisposer' | 'resourceInvalidate' | 'resourcePagination' | 'runtimeInternalEntry' | 'scopeKeyed' | 'scopeWhileWhen' | 'stateActivity' | 'surfaceView' | 'useCommands';
|
|
4
4
|
declare const RETIRED_MESSAGES: Readonly<Record<MessageIds, string>>;
|
|
5
5
|
export { RETIRED_MESSAGES };
|
|
6
6
|
export type { MessageIds };
|