@opetope/lint 0.12.1 → 0.12.3

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 CHANGED
@@ -1,5 +1,72 @@
1
1
  # @opetope/lint
2
2
 
3
+ ## 0.12.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 7a33768: Bind lazy feature body implementations by their declared keys without repeating targets or priorities. Preserve
8
+ builder callbacks, infer instance and slot types, and recognize implementation records in model and visibility
9
+ lint rules.
10
+ - 15db20a: Guide lazy feature bodies toward implementation records with a recommended warning and conservative automatic
11
+ fixes for locally verified headers. Preserve callbacks for dynamic declarations and unproven cases.
12
+
13
+ ## 0.12.2
14
+
15
+ ### Patch Changes
16
+
17
+ - 2d1ecfc: Two axes of a Resource state, not one word (D462)
18
+
19
+ `ResourceState` carries two independent fields, `failure` and `pending`, and each is declared on every member of the
20
+ union — `?: never` where the member cannot hold one. Both are therefore read on the whole state in one step, without
21
+ narrowing by `kind` first:
22
+
23
+ ```ts
24
+ const refused = state.failure !== undefined;
25
+ const working = state.kind === 'loading' || state.pending !== undefined;
26
+ ```
27
+
28
+ `ResourceActivity` is gone, `ResourceChannelFailure` is now `ResourceFailure` with an optional `channel`, and
29
+ `failed` carries `failure` instead of a top-level `error`.
30
+
31
+ **Why.** One frequent question — «did something fail?» — was asked in three shapes: `failed.error`,
32
+ `loading.failure`, and `ready.activity.kind === 'failed'`. «Is something in flight?» was asked in two. Both are one
33
+ read each now.
34
+
35
+ **What changes in behaviour.** The priority within an axis is unchanged: at an equal kind the connection outranks
36
+ the refresh. The priority _between_ the axes is gone — a failed channel no longer hides a working one, so a `ready`
37
+ record publishes a dead connection and a running refresh together. That is more information, not less, and it is the
38
+ difference between «the live stream is gone, what is shown will not update» and «a manual reload is running over a
39
+ live stream».
40
+
41
+ `pending` is observable structure, so **every transition of the work axis now publishes a record**. It did not
42
+ before: one projected word hid that axis on a dataless `loading`, which carried no `activity` at all, and under any
43
+ failure, because `failed` outranked `pending`. Measured on a live Resource with both channels, a connection opening
44
+ over a dataless Resource goes from 1 record to 2; a `refresh()` over a healthy `ready` from 5 to 6; and a
45
+ `refresh()` over a `ready` that already carries a dead connection from 5 to 7, because its settling used to cost no
46
+ record at all. Up to two extra publications over the life of one materialization, and they reach the subscribers of
47
+ a `ready` Resource and not only a dataless one. Each carries the fact the axes were split for.
48
+
49
+ **Migration.**
50
+
51
+ | Was | Is now |
52
+ | --------------------------------------------- | ------------------------------------------------- |
53
+ | `state.error` on `failed` | `state.failure.error` |
54
+ | `state.failure.channel` on `loading` | unchanged, and now on every kind |
55
+ | `state.activity.kind === 'failed'` | `state.failure !== undefined` |
56
+ | `state.activity.kind === 'pending'` | `state.pending !== undefined` |
57
+ | `state.activity.operation` | `state.failure?.channel` or `state.pending` |
58
+ | `state.activity.error` | `state.failure?.error` |
59
+ | `ResourceChannelFailure` from `@opetope/core` | `ResourceFailure` |
60
+ | `ResourceActivity` from `@opetope/core` | nothing: read `state.failure` and `state.pending` |
61
+
62
+ `failure.channel` is absent only on a `failed` record whose every configured channel died, where no single channel
63
+ is the answer. `opetope/no-retired-vocabulary` reports `ResourceActivity` at both entries that ever spelled it, and
64
+ reports `state.activity` wherever it is read rather than only inside a `kind === 'loading'` narrowing; the rename to
65
+ `ResourceFailure` is printed by the compiler itself.
66
+
67
+ The inspection protocol is unchanged: `opetope.runtime-activity/4` stays `/4`, and the facts now read the published
68
+ record instead of a second copy of the channel phases.
69
+
3
70
  ## 0.12.1
4
71
 
5
72
  ### Patch Changes
package/README.md CHANGED
@@ -104,6 +104,9 @@ For a contributor checkout, use `npm run ci:type --workspace @opetope/lint` and
104
104
  <a id="capture-command-cleanup"></a>
105
105
  [See `capture-command-cleanup`](../runtime/docs/reference/lint.md#capture-command-cleanup).
106
106
 
107
+ <a id="prefer-lazy-body-record"></a>
108
+ [See `prefer-lazy-body-record`](../runtime/docs/reference/lint.md#prefer-lazy-body-record).
109
+
107
110
  <a id="prefer-model-selection"></a>
108
111
  [See `prefer-model-selection`](../runtime/docs/reference/lint.md#prefer-model-selection).
109
112
 
package/dist/ast.d.ts CHANGED
@@ -16,5 +16,4 @@ declare function isFunctionLike(node: TSESTree.Node): node is FunctionLike;
16
16
  declare function functionName(node: TSESTree.Node): string | undefined;
17
17
  /** Lexical containment by range: the one question a rule that collects nodes and answers later keeps asking. */
18
18
  declare function within(node: TSESTree.Node, outer: TSESTree.Node): boolean;
19
- export type { FunctionLike };
20
19
  export { calleeName, findProperty, functionName, isFunctionLike, memberFirstStep, memberRoot, patternKeys, propertyName, within, };
package/dist/ast.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ast.js","sources":["../src/ast.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\ntype FunctionLike = TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;\n\n/** `slot` and `defineFeature.body` alike: the dotted name of the simple callee shapes an author writes. */\nfunction calleeName(node: TSESTree.CallExpression): string | undefined {\n const { callee } = node;\n\n if (callee.type === AST_NODE_TYPES.Identifier) return callee.name;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return undefined;\n\n if (callee.object.type !== AST_NODE_TYPES.Identifier || callee.property.type !== AST_NODE_TYPES.Identifier) {\n return undefined;\n }\n\n return `${callee.object.name}.${callee.property.name}`;\n}\n\n/** The static name of a property or a destructured key; a computed or dynamic key has none. */\nfunction propertyName(node: TSESTree.Node): string | undefined {\n if (node.type !== AST_NODE_TYPES.Property || node.computed) return undefined;\n\n if (node.key.type === AST_NODE_TYPES.Identifier) return node.key.name;\n\n if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === 'string') return node.key.value;\n\n return undefined;\n}\n\n/** The property of an object literal under a static key. */\nfunction findProperty(node: TSESTree.ObjectExpression, name: string): TSESTree.Property | undefined {\n for (const property of node.properties) {\n if (property.type === AST_NODE_TYPES.Property && propertyName(property) === name) return property;\n }\n\n return undefined;\n}\n\nfunction patternKeys(pattern: TSESTree.ObjectPattern): ReadonlySet<string> {\n const keys = new Set<string>();\n\n for (const property of pattern.properties) {\n const name = propertyName(property);\n\n if (name !== undefined) keys.add(name);\n }\n\n return keys;\n}\n\n/** The identifier at the root of `imports.platform.submitAllowed`. */\nfunction memberRoot(node: TSESTree.MemberExpression): TSESTree.Node {\n let current: TSESTree.Node = node;\n\n while (current.type === AST_NODE_TYPES.MemberExpression) current = current.object;\n\n return current;\n}\n\n/** The first step of `context.imports.platform`, that is `imports`. */\nfunction memberFirstStep(node: TSESTree.MemberExpression): string | undefined {\n let current = node;\n\n while (current.object.type === AST_NODE_TYPES.MemberExpression) current = current.object;\n\n if (current.computed || current.property.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return current.property.name;\n}\n\nfunction isFunctionLike(node: TSESTree.Node): node is FunctionLike {\n return node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression;\n}\n\n/** The binding a function expression is written into, past the wrappers it is passed through. */\nfunction boundName(node: FunctionLike): string | undefined {\n let current: TSESTree.Node = node;\n\n while (\n current.parent.type === AST_NODE_TYPES.CallExpression ||\n current.parent.type === AST_NODE_TYPES.TSAsExpression\n ) {\n current = current.parent;\n }\n\n const { parent } = current;\n\n if (parent.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier) {\n return parent.id.name;\n }\n\n return propertyName(parent);\n}\n\n/** The name a function is written under: its own, or the binding it is assigned to. */\nfunction functionName(node: TSESTree.Node): string | undefined {\n if (node.type === AST_NODE_TYPES.FunctionDeclaration) return node.id?.name;\n\n if (!isFunctionLike(node)) return undefined;\n\n const own = node.type === AST_NODE_TYPES.FunctionExpression ? node.id?.name : undefined;\n\n return own ?? boundName(node);\n}\n\n/** Lexical containment by range: the one question a rule that collects nodes and answers later keeps asking. */\nfunction within(node: TSESTree.Node, outer: TSESTree.Node): boolean {\n return node.range[0] >= outer.range[0] && node.range[1] <= outer.range[1];\n}\n\nexport type { FunctionLike };\nexport {\n calleeName,\n findProperty,\n functionName,\n isFunctionLike,\n memberFirstStep,\n memberRoot,\n patternKeys,\n propertyName,\n within,\n};\n"],"names":["calleeName","node","callee","AST_NODE_TYPES","propertyName","findProperty","name","property","patternKeys","pattern","keys","memberRoot","current","memberFirstStep","isFunctionLike","boundName","parent","functionName","within","outer"],"mappings":"0DAMA,SAASA,EAAWC,EAA6B,CAC/C,KAAM,CAAE,OAAAC,CAAM,EAAKD,EAEnB,GAAIC,EAAO,OAASC,EAAe,WAAY,OAAOD,EAAO,KAE7D,GAAI,EAAAA,EAAO,OAASC,EAAe,kBAAoBD,EAAO,WAE1D,EAAAA,EAAO,OAAO,OAASC,EAAe,YAAcD,EAAO,SAAS,OAASC,EAAe,YAIhG,MAAO,GAAGD,EAAO,OAAO,IAAI,IAAIA,EAAO,SAAS,IAAI,EACtD,CAGA,SAASE,EAAaH,EAAmB,CACvC,GAAI,EAAAA,EAAK,OAASE,EAAe,UAAYF,EAAK,UAElD,IAAIA,EAAK,IAAI,OAASE,EAAe,WAAY,OAAOF,EAAK,IAAI,KAEjE,GAAIA,EAAK,IAAI,OAASE,EAAe,SAAW,OAAOF,EAAK,IAAI,OAAU,SAAU,OAAOA,EAAK,IAAI,MAGtG,CAGA,SAASI,EAAaJ,EAAiCK,EAAY,CACjE,UAAWC,KAAYN,EAAK,WAC1B,GAAIM,EAAS,OAASJ,EAAe,UAAYC,EAAaG,CAAQ,IAAMD,EAAM,OAAOC,CAI7F,CAEA,SAASC,EAAYC,EAA+B,CAClD,MAAMC,EAAO,IAAI,IAEjB,UAAWH,KAAYE,EAAQ,WAAY,CACzC,MAAMH,EAAOF,EAAaG,CAAQ,EAE9BD,IAAS,QAAWI,EAAK,IAAIJ,CAAI,CACvC,CAEA,OAAOI,CACT,CAGA,SAASC,EAAWV,EAA+B,CACjD,IAAIW,EAAyBX,EAE7B,KAAOW,EAAQ,OAAST,EAAe,kBAAkBS,EAAUA,EAAQ,OAE3E,OAAOA,CACT,CAGA,SAASC,EAAgBZ,EAA+B,CACtD,IAAIW,EAAUX,EAEd,KAAOW,EAAQ,OAAO,OAAST,EAAe,kBAAkBS,EAAUA,EAAQ,OAElF,GAAI,EAAAA,EAAQ,UAAYA,EAAQ,SAAS,OAAST,EAAe,YAEjE,OAAOS,EAAQ,SAAS,IAC1B,CAEA,SAASE,EAAeb,EAAmB,CACzC,OAAOA,EAAK,OAASE,EAAe,yBAA2BF,EAAK,OAASE,EAAe,kBAC9F,CAGA,SAASY,EAAUd,EAAkB,CACnC,IAAIW,EAAyBX,EAE7B,KACEW,EAAQ,OAAO,OAAST,EAAe,gBACvCS,EAAQ,OAAO,OAAST,EAAe,gBAEvCS,EAAUA,EAAQ,OAGpB,KAAM,CAAE,OAAAI,CAAM,EAAKJ,EAEnB,OAAII,EAAO,OAASb,EAAe,oBAAsBa,EAAO,GAAG,OAASb,EAAe,WAClFa,EAAO,GAAG,KAGZZ,EAAaY,CAAM,CAC5B,CAGA,SAASC,EAAahB,EAAmB,CACvC,OAAIA,EAAK,OAASE,EAAe,oBAA4BF,EAAK,IAAI,KAEjEa,EAAeb,CAAI,GAEZA,EAAK,OAASE,EAAe,mBAAqBF,EAAK,IAAI,KAAO,SAEhEc,EAAUd,CAAI,EAJD,MAK7B,CAGA,SAASiB,EAAOjB,EAAqBkB,EAAoB,CACvD,OAAOlB,EAAK,MAAM,CAAC,GAAKkB,EAAM,MAAM,CAAC,GAAKlB,EAAK,MAAM,CAAC,GAAKkB,EAAM,MAAM,CAAC,CAC1E"}
1
+ {"version":3,"file":"ast.js","sources":["../src/ast.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\ntype FunctionLike = TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;\n\n/** `slot` and `defineFeature.body` alike: the dotted name of the simple callee shapes an author writes. */\nfunction calleeName(node: TSESTree.CallExpression): string | undefined {\n const { callee } = node;\n\n if (callee.type === AST_NODE_TYPES.Identifier) return callee.name;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return undefined;\n\n if (callee.object.type !== AST_NODE_TYPES.Identifier || callee.property.type !== AST_NODE_TYPES.Identifier) {\n return undefined;\n }\n\n return `${callee.object.name}.${callee.property.name}`;\n}\n\n/** The static name of a property or a destructured key; a computed or dynamic key has none. */\nfunction propertyName(node: TSESTree.Node): string | undefined {\n if (node.type !== AST_NODE_TYPES.Property || node.computed) return undefined;\n\n if (node.key.type === AST_NODE_TYPES.Identifier) return node.key.name;\n\n if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === 'string') return node.key.value;\n\n return undefined;\n}\n\n/** The property of an object literal under a static key. */\nfunction findProperty(node: TSESTree.ObjectExpression, name: string): TSESTree.Property | undefined {\n for (const property of node.properties) {\n if (property.type === AST_NODE_TYPES.Property && propertyName(property) === name) return property;\n }\n\n return undefined;\n}\n\nfunction patternKeys(pattern: TSESTree.ObjectPattern): ReadonlySet<string> {\n const keys = new Set<string>();\n\n for (const property of pattern.properties) {\n const name = propertyName(property);\n\n if (name !== undefined) keys.add(name);\n }\n\n return keys;\n}\n\n/** The identifier at the root of `imports.platform.submitAllowed`. */\nfunction memberRoot(node: TSESTree.MemberExpression): TSESTree.Node {\n let current: TSESTree.Node = node;\n\n while (current.type === AST_NODE_TYPES.MemberExpression) current = current.object;\n\n return current;\n}\n\n/** The first step of `context.imports.platform`, that is `imports`. */\nfunction memberFirstStep(node: TSESTree.MemberExpression): string | undefined {\n let current = node;\n\n while (current.object.type === AST_NODE_TYPES.MemberExpression) current = current.object;\n\n if (current.computed || current.property.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return current.property.name;\n}\n\nfunction isFunctionLike(node: TSESTree.Node): node is FunctionLike {\n return node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression;\n}\n\n/** The binding a function expression is written into, past the wrappers it is passed through. */\nfunction boundName(node: FunctionLike): string | undefined {\n let current: TSESTree.Node = node;\n\n while (\n current.parent.type === AST_NODE_TYPES.CallExpression ||\n current.parent.type === AST_NODE_TYPES.TSAsExpression\n ) {\n current = current.parent;\n }\n\n const { parent } = current;\n\n if (parent.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier) {\n return parent.id.name;\n }\n\n return propertyName(parent);\n}\n\n/** The name a function is written under: its own, or the binding it is assigned to. */\nfunction functionName(node: TSESTree.Node): string | undefined {\n if (node.type === AST_NODE_TYPES.FunctionDeclaration) return node.id?.name;\n\n if (!isFunctionLike(node)) return undefined;\n\n const own = node.type === AST_NODE_TYPES.FunctionExpression ? node.id?.name : undefined;\n\n return own ?? boundName(node);\n}\n\n/** Lexical containment by range: the one question a rule that collects nodes and answers later keeps asking. */\nfunction within(node: TSESTree.Node, outer: TSESTree.Node): boolean {\n return node.range[0] >= outer.range[0] && node.range[1] <= outer.range[1];\n}\n\nexport {\n calleeName,\n findProperty,\n functionName,\n isFunctionLike,\n memberFirstStep,\n memberRoot,\n patternKeys,\n propertyName,\n within,\n};\n"],"names":["calleeName","node","callee","AST_NODE_TYPES","propertyName","findProperty","name","property","patternKeys","pattern","keys","memberRoot","current","memberFirstStep","isFunctionLike","boundName","parent","functionName","within","outer"],"mappings":"0DAMA,SAASA,EAAWC,EAA6B,CAC/C,KAAM,CAAE,OAAAC,CAAM,EAAKD,EAEnB,GAAIC,EAAO,OAASC,EAAe,WAAY,OAAOD,EAAO,KAE7D,GAAI,EAAAA,EAAO,OAASC,EAAe,kBAAoBD,EAAO,WAE1D,EAAAA,EAAO,OAAO,OAASC,EAAe,YAAcD,EAAO,SAAS,OAASC,EAAe,YAIhG,MAAO,GAAGD,EAAO,OAAO,IAAI,IAAIA,EAAO,SAAS,IAAI,EACtD,CAGA,SAASE,EAAaH,EAAmB,CACvC,GAAI,EAAAA,EAAK,OAASE,EAAe,UAAYF,EAAK,UAElD,IAAIA,EAAK,IAAI,OAASE,EAAe,WAAY,OAAOF,EAAK,IAAI,KAEjE,GAAIA,EAAK,IAAI,OAASE,EAAe,SAAW,OAAOF,EAAK,IAAI,OAAU,SAAU,OAAOA,EAAK,IAAI,MAGtG,CAGA,SAASI,EAAaJ,EAAiCK,EAAY,CACjE,UAAWC,KAAYN,EAAK,WAC1B,GAAIM,EAAS,OAASJ,EAAe,UAAYC,EAAaG,CAAQ,IAAMD,EAAM,OAAOC,CAI7F,CAEA,SAASC,EAAYC,EAA+B,CAClD,MAAMC,EAAO,IAAI,IAEjB,UAAWH,KAAYE,EAAQ,WAAY,CACzC,MAAMH,EAAOF,EAAaG,CAAQ,EAE9BD,IAAS,QAAWI,EAAK,IAAIJ,CAAI,CACvC,CAEA,OAAOI,CACT,CAGA,SAASC,EAAWV,EAA+B,CACjD,IAAIW,EAAyBX,EAE7B,KAAOW,EAAQ,OAAST,EAAe,kBAAkBS,EAAUA,EAAQ,OAE3E,OAAOA,CACT,CAGA,SAASC,EAAgBZ,EAA+B,CACtD,IAAIW,EAAUX,EAEd,KAAOW,EAAQ,OAAO,OAAST,EAAe,kBAAkBS,EAAUA,EAAQ,OAElF,GAAI,EAAAA,EAAQ,UAAYA,EAAQ,SAAS,OAAST,EAAe,YAEjE,OAAOS,EAAQ,SAAS,IAC1B,CAEA,SAASE,EAAeb,EAAmB,CACzC,OAAOA,EAAK,OAASE,EAAe,yBAA2BF,EAAK,OAASE,EAAe,kBAC9F,CAGA,SAASY,EAAUd,EAAkB,CACnC,IAAIW,EAAyBX,EAE7B,KACEW,EAAQ,OAAO,OAAST,EAAe,gBACvCS,EAAQ,OAAO,OAAST,EAAe,gBAEvCS,EAAUA,EAAQ,OAGpB,KAAM,CAAE,OAAAI,CAAM,EAAKJ,EAEnB,OAAII,EAAO,OAASb,EAAe,oBAAsBa,EAAO,GAAG,OAASb,EAAe,WAClFa,EAAO,GAAG,KAGZZ,EAAaY,CAAM,CAC5B,CAGA,SAASC,EAAahB,EAAmB,CACvC,OAAIA,EAAK,OAASE,EAAe,oBAA4BF,EAAK,IAAI,KAEjEa,EAAeb,CAAI,GAEZA,EAAK,OAASE,EAAe,mBAAqBF,EAAK,IAAI,KAAO,SAEhEc,EAAUd,CAAI,EAJD,MAK7B,CAGA,SAASiB,EAAOjB,EAAqBkB,EAAoB,CACvD,OAAOlB,EAAK,MAAM,CAAC,GAAKkB,EAAM,MAAM,CAAC,GAAKlB,EAAK,MAAM,CAAC,GAAKkB,EAAM,MAAM,CAAC,CAC1E"}
@@ -0,0 +1,8 @@
1
+ import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ interface BodyRecordEntry {
3
+ readonly implementation: TSESTree.Node | undefined;
4
+ readonly options: TSESTree.Node | undefined;
5
+ }
6
+ /** Resolve local record/tuple aliases only after identifying the authentic authoring API by its import. */
7
+ declare function bodyRecordEntries(source: TSESLint.SourceCode, call: TSESTree.CallExpression): readonly BodyRecordEntry[];
8
+ export { bodyRecordEntries };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as r}from"@typescript-eslint/utils";import{findProperty as f}from"./ast.js";import{localValue as p,isApi as s}from"./model-bindings.js";function a(n,t){const e=p(n,t.callee);return e.type!==r.MemberExpression||e.computed||e.property.type!==r.Identifier||e.property.name!=="body"?!1:s(n,e.object,"@opetope/runtime","defineFeature")}function l(n,t){if(!a(n,t))return;const e=t.arguments[1];if(e===void 0)return;const i=p(n,e);return i.type===r.ObjectExpression?i:void 0}function y(n,t){const e=l(n,t);if(e===void 0)return[];const i=f(e,"provides");if(i===void 0)return[];const u=p(n,i.value);return u.type!==r.ObjectExpression?[]:u.properties.flatMap(d=>{if(d.type!==r.Property)return[];const o=p(n,d.value);return[{implementation:o.type===r.ArrayExpression?o.elements[0]??void 0:o,options:o.type===r.ArrayExpression?o.elements[1]??void 0:void 0}]})}export{y as bodyRecordEntries};
2
+ //# sourceMappingURL=feature-body-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-body-records.js","sources":["../src/feature-body-records.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty } from './ast';\nimport { isApi, localValue } from './model-bindings';\n\ninterface BodyRecordEntry {\n readonly implementation: TSESTree.Node | undefined;\n readonly options: TSESTree.Node | undefined;\n}\n\nfunction isBodyCall(source: TSESLint.SourceCode, call: TSESTree.CallExpression): boolean {\n const callee = localValue(source, call.callee);\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false;\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'body') return false;\n return isApi(source, callee.object, '@opetope/runtime', 'defineFeature');\n}\n\nfunction bodySections(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n): TSESTree.ObjectExpression | undefined {\n if (!isBodyCall(source, call)) return undefined;\n const input = call.arguments[1];\n if (input === undefined) return undefined;\n const value = localValue(source, input);\n return value.type === AST_NODE_TYPES.ObjectExpression ? value : undefined;\n}\n\n/** Resolve local record/tuple aliases only after identifying the authentic authoring API by its import. */\nfunction bodyRecordEntries(source: TSESLint.SourceCode, call: TSESTree.CallExpression): readonly BodyRecordEntry[] {\n const sections = bodySections(source, call);\n if (sections === undefined) return [];\n const property = findProperty(sections, 'provides');\n if (property === undefined) return [];\n const record = localValue(source, property.value);\n if (record.type !== AST_NODE_TYPES.ObjectExpression) return [];\n return record.properties.flatMap(entry => {\n if (entry.type !== AST_NODE_TYPES.Property) return [];\n const value = localValue(source, entry.value);\n return [\n {\n implementation: value.type === AST_NODE_TYPES.ArrayExpression ? (value.elements[0] ?? undefined) : value,\n options: value.type === AST_NODE_TYPES.ArrayExpression ? (value.elements[1] ?? undefined) : undefined,\n },\n ];\n });\n}\n\nexport { bodyRecordEntries };\n"],"names":["isBodyCall","source","call","callee","localValue","AST_NODE_TYPES","isApi","bodySections","input","value","bodyRecordEntries","sections","property","findProperty","record","entry"],"mappings":"8JAWA,SAASA,EAAWC,EAA6BC,EAA6B,CAC5E,MAAMC,EAASC,EAAWH,EAAQC,EAAK,MAAM,EAE7C,OADIC,EAAO,OAASE,EAAe,kBAAoBF,EAAO,UAC1DA,EAAO,SAAS,OAASE,EAAe,YAAcF,EAAO,SAAS,OAAS,OAAe,GAC3FG,EAAML,EAAQE,EAAO,OAAQ,mBAAoB,eAAe,CACzE,CAEA,SAASI,EACPN,EACAC,EAA6B,CAE7B,GAAI,CAACF,EAAWC,EAAQC,CAAI,EAAG,OAC/B,MAAMM,EAAQN,EAAK,UAAU,CAAC,EAC9B,GAAIM,IAAU,OAAW,OACzB,MAAMC,EAAQL,EAAWH,EAAQO,CAAK,EACtC,OAAOC,EAAM,OAASJ,EAAe,iBAAmBI,EAAQ,MAClE,CAGA,SAASC,EAAkBT,EAA6BC,EAA6B,CACnF,MAAMS,EAAWJ,EAAaN,EAAQC,CAAI,EAC1C,GAAIS,IAAa,OAAW,MAAO,CAAA,EACnC,MAAMC,EAAWC,EAAaF,EAAU,UAAU,EAClD,GAAIC,IAAa,OAAW,MAAO,CAAA,EACnC,MAAME,EAASV,EAAWH,EAAQW,EAAS,KAAK,EAChD,OAAIE,EAAO,OAAST,EAAe,iBAAyB,CAAA,EACrDS,EAAO,WAAW,QAAQC,GAAQ,CACvC,GAAIA,EAAM,OAASV,EAAe,SAAU,MAAO,CAAA,EACnD,MAAMI,EAAQL,EAAWH,EAAQc,EAAM,KAAK,EAC5C,MAAO,CACL,CACE,eAAgBN,EAAM,OAASJ,EAAe,gBAAmBI,EAAM,SAAS,CAAC,GAAK,OAAaA,EACnG,QAASA,EAAM,OAASJ,EAAe,gBAAmBI,EAAM,SAAS,CAAC,GAAK,OAAa,MAC7F,EAEL,CAAC,CACH"}
package/dist/index.d.ts CHANGED
@@ -44,7 +44,7 @@ declare const plugin: {
44
44
  'no-redundant-const-tuple': import("@typescript-eslint/utils/ts-eslint").RuleModule<"redundantConstTuple", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
45
45
  name: string;
46
46
  };
47
- 'no-retired-vocabulary': import("@typescript-eslint/utils/ts-eslint").RuleModule<"commandLane" | "commandLatest" | "commandName" | "commandOnce" | "commandOutcomeStatus" | "commandPolicy" | "commandQueueBy" | "conditionFrom" | "contextCall" | "contextCalls" | "contributionWhen" | "deliveryKey" | "deliveryKind" | "deliveryReject" | "devtoolsResourceActivity" | "effectOnDispose" | "effectSkipInitial" | "effectWhen" | "eventBackpressure" | "fromExternal" | "loadingActivity" | "navigatorPolicy" | "resourceDisposer" | "resourceInvalidate" | "runtimeInternalEntry" | "scopeKeyed" | "useCommands", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
47
+ 'no-retired-vocabulary': import("@typescript-eslint/utils/ts-eslint").RuleModule<"commandLane" | "commandLatest" | "commandName" | "commandOnce" | "commandOutcomeStatus" | "commandPolicy" | "commandQueueBy" | "conditionFrom" | "contextCall" | "contextCalls" | "contributionWhen" | "deliveryKey" | "deliveryKind" | "deliveryReject" | "effectOnDispose" | "effectSkipInitial" | "effectWhen" | "eventBackpressure" | "fromExternal" | "navigatorPolicy" | "resourceActivity" | "resourceDisposer" | "resourceInvalidate" | "runtimeInternalEntry" | "scopeKeyed" | "stateActivity" | "useCommands", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
48
48
  name: string;
49
49
  };
50
50
  'no-snapshot-in-update': import("@typescript-eslint/utils/ts-eslint").RuleModule<"snapshotInUpdate" | "writeFromPrevious", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
@@ -62,6 +62,9 @@ declare const plugin: {
62
62
  'prefer-effect-current': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferEffectCurrent", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
63
63
  name: string;
64
64
  };
65
+ 'prefer-lazy-body-record': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferRecord", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
66
+ name: string;
67
+ };
65
68
  'prefer-model-selection': import("@typescript-eslint/utils/ts-eslint").RuleModule<"separateHooks", readonly [{
66
69
  readonly threshold: number;
67
70
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
@@ -118,6 +121,7 @@ declare const configuredPlugin: {
118
121
  'opetope/no-subscribe-outside-models': "off";
119
122
  'opetope/no-write-after-source-write': "error";
120
123
  'opetope/prefer-effect-current': "error";
124
+ 'opetope/prefer-lazy-body-record': "warn";
121
125
  'opetope/prefer-model-selection': "off";
122
126
  'opetope/require-declared-models': "error";
123
127
  'opetope/require-literal-id': "error";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{captureCommandCleanup as p}from"./rules/capture-command-cleanup.js";import{defineFeaturePropertyOrder as s}from"./rules/define-feature-property-order.js";import{idNaming as i}from"./rules/id-naming.js";import{layerPlacement as a}from"./rules/layer-placement.js";import{noCommandInDeps as m}from"./rules/no-command-in-deps.js";import{noInternalImports as l}from"./rules/no-internal-imports.js";import{noRedundantConstTuple as d}from"./rules/no-redundant-const-tuple.js";import{noRetiredVocabulary as c}from"./rules/no-retired-vocabulary.js";import{noSnapshotInUpdate as u}from"./rules/no-snapshot-in-update.js";import{noSnapshotReadInRender as f}from"./rules/no-snapshot-read-in-render.js";import{noSubscribeOutsideModels as b}from"./rules/no-subscribe-outside-models.js";import{noWriteAfterSourceWrite as g}from"./rules/no-write-after-source-write.js";import{preferEffectCurrent as y}from"./rules/prefer-effect-current.js";import{preferModelSelection as S}from"./rules/prefer-model-selection.js";import{requireDeclaredModels as h}from"./rules/require-declared-models.js";import{requireLiteralId as I}from"./rules/require-literal-id.js";import{enabledPredicate as q}from"./rules/enabled-predicate.js";const C=["integration","models","ui"],E=["**/*.ts","**/*.tsx"],R=["**/__tests__/**","**/*.spec.ts","**/*.spec.tsx"],_={meta:{name:"@opetope/lint",version:"0.12.1"},rules:{"capture-command-cleanup":p,"define-feature-property-order":s,"id-naming":i,"layer-placement":a,"no-command-in-deps":m,"no-internal-imports":l,"no-redundant-const-tuple":d,"no-retired-vocabulary":c,"no-snapshot-in-update":u,"no-snapshot-read-in-render":f,"no-subscribe-outside-models":b,"no-write-after-source-write":g,"prefer-effect-current":y,"prefer-model-selection":S,"require-declared-models":h,"require-literal-id":I,"enabled-predicate":q}},r=_,j={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-command-in-deps":"error","opetope/no-internal-imports":"error","opetope/no-redundant-const-tuple":"error","opetope/no-retired-vocabulary":"error","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-effect-current":"error","opetope/prefer-model-selection":"off","opetope/require-declared-models":"error","opetope/require-literal-id":"error","opetope/enabled-predicate":"error"}},w=e=>C.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}]}}]}),x=({models:e=[],tests:o=R})=>e.length===0?[]:[{files:E,name:"opetope/layers-subscriptions",plugins:{opetope:r},rules:{"opetope/no-subscribe-outside-models":"error"}},{files:[...e,...o],name:"opetope/layers-subscriptions-allowed",rules:{"opetope/no-subscribe-outside-models":"off"}}],L=e=>[...w(e),...x(e)],P=({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"}}]],v=Object.assign(r,{configs:{internalImports:P,layers:L,recommended:j}}),n=v;export{n as default,n as opetopeLint};
1
+ import{captureCommandCleanup as p}from"./rules/capture-command-cleanup.js";import{defineFeaturePropertyOrder as s}from"./rules/define-feature-property-order.js";import{idNaming as i}from"./rules/id-naming.js";import{layerPlacement as a}from"./rules/layer-placement.js";import{noCommandInDeps as m}from"./rules/no-command-in-deps.js";import{noInternalImports as l}from"./rules/no-internal-imports.js";import{noRedundantConstTuple as d}from"./rules/no-redundant-const-tuple.js";import{noRetiredVocabulary as c}from"./rules/no-retired-vocabulary.js";import{noSnapshotInUpdate as u}from"./rules/no-snapshot-in-update.js";import{noSnapshotReadInRender as f}from"./rules/no-snapshot-read-in-render.js";import{noSubscribeOutsideModels as b}from"./rules/no-subscribe-outside-models.js";import{noWriteAfterSourceWrite as y}from"./rules/no-write-after-source-write.js";import{preferEffectCurrent as g}from"./rules/prefer-effect-current.js";import{preferLazyBodyRecord as S}from"./rules/prefer-lazy-body-record.js";import{preferModelSelection as h}from"./rules/prefer-model-selection.js";import{requireDeclaredModels as I}from"./rules/require-declared-models.js";import{requireLiteralId as R}from"./rules/require-literal-id.js";import{enabledPredicate as q}from"./rules/enabled-predicate.js";const w=["integration","models","ui"],C=["**/*.ts","**/*.tsx"],E=["**/__tests__/**","**/*.spec.ts","**/*.spec.tsx"],L={meta:{name:"@opetope/lint",version:"0.12.3"},rules:{"capture-command-cleanup":p,"define-feature-property-order":s,"id-naming":i,"layer-placement":a,"no-command-in-deps":m,"no-internal-imports":l,"no-redundant-const-tuple":d,"no-retired-vocabulary":c,"no-snapshot-in-update":u,"no-snapshot-read-in-render":f,"no-subscribe-outside-models":b,"no-write-after-source-write":y,"prefer-effect-current":g,"prefer-lazy-body-record":S,"prefer-model-selection":h,"require-declared-models":I,"require-literal-id":R,"enabled-predicate":q}},r=L,_={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-command-in-deps":"error","opetope/no-internal-imports":"error","opetope/no-redundant-const-tuple":"error","opetope/no-retired-vocabulary":"error","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-effect-current":"error","opetope/prefer-lazy-body-record":"warn","opetope/prefer-model-selection":"off","opetope/require-declared-models":"error","opetope/require-literal-id":"error","opetope/enabled-predicate":"error"}},j=e=>w.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}]}}]}),x=({models:e=[],tests:o=E})=>e.length===0?[]:[{files:C,name:"opetope/layers-subscriptions",plugins:{opetope:r},rules:{"opetope/no-subscribe-outside-models":"error"}},{files:[...e,...o],name:"opetope/layers-subscriptions-allowed",rules:{"opetope/no-subscribe-outside-models":"off"}}],P=e=>[...j(e),...x(e)],v=({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"}}]],M=Object.assign(r,{configs:{internalImports:v,layers:P,recommended:_}}),n=M;export{n as default,n 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 { noCommandInDeps } from './rules/no-command-in-deps';\nimport { noInternalImports } from './rules/no-internal-imports';\nimport { noRedundantConstTuple } from './rules/no-redundant-const-tuple';\nimport { noRetiredVocabulary } from './rules/no-retired-vocabulary';\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 { preferEffectCurrent } from './rules/prefer-effect-current';\nimport { preferModelSelection } from './rules/prefer-model-selection';\nimport { requireDeclaredModels } from './rules/require-declared-models';\nimport { requireLiteralId } from './rules/require-literal-id';\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 readonly integration?: readonly string[];\n readonly models?: readonly string[];\n readonly tests?: readonly string[];\n readonly ui?: readonly string[];\n};\n\ntype InternalImportOptions = {\n readonly allow?: readonly string[];\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.12.1' },\n rules: {\n 'capture-command-cleanup': captureCommandCleanup,\n 'define-feature-property-order': defineFeaturePropertyOrder,\n 'id-naming': idNaming,\n 'layer-placement': layerPlacement,\n 'no-command-in-deps': noCommandInDeps,\n 'no-internal-imports': noInternalImports,\n 'no-redundant-const-tuple': noRedundantConstTuple,\n 'no-retired-vocabulary': noRetiredVocabulary,\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-effect-current': preferEffectCurrent,\n 'prefer-model-selection': preferModelSelection,\n 'require-declared-models': requireDeclaredModels,\n 'require-literal-id': requireLiteralId,\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-command-in-deps': 'error',\n 'opetope/no-internal-imports': 'error',\n 'opetope/no-redundant-const-tuple': 'error',\n 'opetope/no-retired-vocabulary': 'error',\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-effect-current': 'error',\n 'opetope/prefer-model-selection': 'off',\n 'opetope/require-declared-models': 'error',\n 'opetope/require-literal-id': '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.\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: { 'opetope/no-subscribe-outside-models': 'error' },\n },\n {\n files: [...models, ...tests],\n name: 'opetope/layers-subscriptions-allowed',\n rules: { 'opetope/no-subscribe-outside-models': 'off' },\n },\n ];\n\nconst layers = (options: LayerOptions): readonly Linter.Config[] => [...placement(options), ...subscriptions(options)];\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","noCommandInDeps","noInternalImports","noRedundantConstTuple","noRetiredVocabulary","noSnapshotInUpdate","noSnapshotReadInRender","noSubscribeOutsideModels","noWriteAfterSourceWrite","preferEffectCurrent","preferModelSelection","requireDeclaredModels","requireLiteralId","enabledPredicate","nativePlugin","recommended","placement","options","layer","files","subscriptions","models","tests","layers","internalImports","allow","configuredPlugin","opetopeLint"],"mappings":"urCAoCA,MAAMA,EAAS,CAAC,cAAe,SAAU,IAAI,EACvCC,EAAe,CAAC,UAAW,UAAU,EACrCC,EAAa,CAAC,kBAAmB,eAAgB,eAAe,EAEhEC,EAAS,CACb,KAAM,CAAE,KAAM,gBAAiB,QAAS,QAAQ,EAChD,MAAO,CACL,0BAA2BC,EAC3B,gCAAiCC,EACjC,YAAaC,EACb,kBAAmBC,EACnB,qBAAsBC,EACtB,sBAAuBC,EACvB,2BAA4BC,EAC5B,wBAAyBC,EACzB,wBAAyBC,EACzB,6BAA8BC,EAC9B,8BAA+BC,EAC/B,8BAA+BC,EAC/B,wBAAyBC,EACzB,yBAA0BC,EAC1B,0BAA2BC,EAC3B,qBAAsBC,EACtB,oBAAqBC,CACtB,GAcGC,EAAelB,EAEfmB,EAAc,CAClB,KAAM,sBACN,QAAS,CAAE,QAASD,CAAY,EAChC,MAAO,CACL,kCAAmC,QACnC,wCAAyC,QACzC,oBAAqB,QACrB,0BAA2B,MAC3B,6BAA8B,QAC9B,8BAA+B,QAC/B,mCAAoC,QACpC,gCAAiC,QACjC,gCAAiC,QACjC,qCAAsC,QACtC,sCAAuC,MACvC,sCAAuC,QACvC,gCAAiC,QACjC,iCAAkC,MAClC,kCAAmC,QACnC,6BAA8B,QAC9B,4BAA6B,OAC9B,GAOGE,EAAaC,GACjBxB,EAAO,QAAQyB,GAAQ,CACrB,MAAMC,EAAQF,EAAQC,CAAK,GAAK,CAAA,EAEhC,OAAIC,EAAM,SAAW,EAAU,CAAA,EAExB,CACL,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,kBAAkBD,CAAK,GAC7B,QAAS,CAAE,QAASJ,CAAY,EAChC,MAAO,CAAE,0BAA2B,CAAC,QAAS,CAAE,MAAAI,CAAK,CAAE,CAAC,CACjC,EAE7B,CAAC,EAOGE,EAAgB,CAAC,CAAE,OAAAC,EAAS,CAAA,EAAI,MAAAC,EAAQ3B,CAAU,IACtD0B,EAAO,SAAW,EACd,CAAA,EACA,CACE,CACE,MAAO3B,EACP,KAAM,+BACN,QAAS,CAAE,QAASoB,CAAY,EAChC,MAAO,CAAE,sCAAuC,OAAO,CACxD,EACD,CACE,MAAO,CAAC,GAAGO,EAAQ,GAAGC,CAAK,EAC3B,KAAM,uCACN,MAAO,CAAE,sCAAuC,KAAK,CACtD,GAGHC,EAAUN,GAAoD,CAAC,GAAGD,EAAUC,CAAO,EAAG,GAAGG,EAAcH,CAAO,CAAC,EAO/GO,EAAkB,CAAC,CACvB,MAAAC,EAAQ,CAAA,EACR,MAAAN,EAAQ,CAAC,sCAAsC,CAAC,EACvB,KAAiC,CAC1D,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,2BACN,QAAS,CAAE,QAASL,CAAY,EAChC,MAAO,CAAE,8BAA+B,OAAO,CAChD,EACD,GAAIW,EAAM,SAAW,EACjB,CAAA,EACA,CACE,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,mCACN,MAAO,CAAE,8BAA+B,KAAc,CACvD,IAIHC,EAAmB,OAAO,OAAOZ,EAAc,CACnD,QAAS,CAAE,gBAAAU,EAAiB,OAAAD,EAAQ,YAAAR,CAAW,CAChD,CAAA,EAKKY,EAAcD"}
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 { noCommandInDeps } from './rules/no-command-in-deps';\nimport { noInternalImports } from './rules/no-internal-imports';\nimport { noRedundantConstTuple } from './rules/no-redundant-const-tuple';\nimport { noRetiredVocabulary } from './rules/no-retired-vocabulary';\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 { preferEffectCurrent } from './rules/prefer-effect-current';\nimport { preferLazyBodyRecord } from './rules/prefer-lazy-body-record';\nimport { preferModelSelection } from './rules/prefer-model-selection';\nimport { requireDeclaredModels } from './rules/require-declared-models';\nimport { requireLiteralId } from './rules/require-literal-id';\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 readonly integration?: readonly string[];\n readonly models?: readonly string[];\n readonly tests?: readonly string[];\n readonly ui?: readonly string[];\n};\n\ntype InternalImportOptions = {\n readonly allow?: readonly string[];\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.12.3' },\n rules: {\n 'capture-command-cleanup': captureCommandCleanup,\n 'define-feature-property-order': defineFeaturePropertyOrder,\n 'id-naming': idNaming,\n 'layer-placement': layerPlacement,\n 'no-command-in-deps': noCommandInDeps,\n 'no-internal-imports': noInternalImports,\n 'no-redundant-const-tuple': noRedundantConstTuple,\n 'no-retired-vocabulary': noRetiredVocabulary,\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-effect-current': preferEffectCurrent,\n 'prefer-lazy-body-record': preferLazyBodyRecord,\n 'prefer-model-selection': preferModelSelection,\n 'require-declared-models': requireDeclaredModels,\n 'require-literal-id': requireLiteralId,\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-command-in-deps': 'error',\n 'opetope/no-internal-imports': 'error',\n 'opetope/no-redundant-const-tuple': 'error',\n 'opetope/no-retired-vocabulary': 'error',\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-effect-current': 'error',\n 'opetope/prefer-lazy-body-record': 'warn',\n 'opetope/prefer-model-selection': 'off',\n 'opetope/require-declared-models': 'error',\n 'opetope/require-literal-id': '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.\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: { 'opetope/no-subscribe-outside-models': 'error' },\n },\n {\n files: [...models, ...tests],\n name: 'opetope/layers-subscriptions-allowed',\n rules: { 'opetope/no-subscribe-outside-models': 'off' },\n },\n ];\n\nconst layers = (options: LayerOptions): readonly Linter.Config[] => [...placement(options), ...subscriptions(options)];\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","noCommandInDeps","noInternalImports","noRedundantConstTuple","noRetiredVocabulary","noSnapshotInUpdate","noSnapshotReadInRender","noSubscribeOutsideModels","noWriteAfterSourceWrite","preferEffectCurrent","preferLazyBodyRecord","preferModelSelection","requireDeclaredModels","requireLiteralId","enabledPredicate","nativePlugin","recommended","placement","options","layer","files","subscriptions","models","tests","layers","internalImports","allow","configuredPlugin","opetopeLint"],"mappings":"iwCAqCA,MAAMA,EAAS,CAAC,cAAe,SAAU,IAAI,EACvCC,EAAe,CAAC,UAAW,UAAU,EACrCC,EAAa,CAAC,kBAAmB,eAAgB,eAAe,EAEhEC,EAAS,CACb,KAAM,CAAE,KAAM,gBAAiB,QAAS,QAAQ,EAChD,MAAO,CACL,0BAA2BC,EAC3B,gCAAiCC,EACjC,YAAaC,EACb,kBAAmBC,EACnB,qBAAsBC,EACtB,sBAAuBC,EACvB,2BAA4BC,EAC5B,wBAAyBC,EACzB,wBAAyBC,EACzB,6BAA8BC,EAC9B,8BAA+BC,EAC/B,8BAA+BC,EAC/B,wBAAyBC,EACzB,0BAA2BC,EAC3B,yBAA0BC,EAC1B,0BAA2BC,EAC3B,qBAAsBC,EACtB,oBAAqBC,CACtB,GAcGC,EAAenB,EAEfoB,EAAc,CAClB,KAAM,sBACN,QAAS,CAAE,QAASD,CAAY,EAChC,MAAO,CACL,kCAAmC,QACnC,wCAAyC,QACzC,oBAAqB,QACrB,0BAA2B,MAC3B,6BAA8B,QAC9B,8BAA+B,QAC/B,mCAAoC,QACpC,gCAAiC,QACjC,gCAAiC,QACjC,qCAAsC,QACtC,sCAAuC,MACvC,sCAAuC,QACvC,gCAAiC,QACjC,kCAAmC,OACnC,iCAAkC,MAClC,kCAAmC,QACnC,6BAA8B,QAC9B,4BAA6B,OAC9B,GAOGE,EAAaC,GACjBzB,EAAO,QAAQ0B,GAAQ,CACrB,MAAMC,EAAQF,EAAQC,CAAK,GAAK,CAAA,EAEhC,OAAIC,EAAM,SAAW,EAAU,CAAA,EAExB,CACL,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,kBAAkBD,CAAK,GAC7B,QAAS,CAAE,QAASJ,CAAY,EAChC,MAAO,CAAE,0BAA2B,CAAC,QAAS,CAAE,MAAAI,CAAK,CAAE,CAAC,CACjC,EAE7B,CAAC,EAOGE,EAAgB,CAAC,CAAE,OAAAC,EAAS,CAAA,EAAI,MAAAC,EAAQ5B,CAAU,IACtD2B,EAAO,SAAW,EACd,CAAA,EACA,CACE,CACE,MAAO5B,EACP,KAAM,+BACN,QAAS,CAAE,QAASqB,CAAY,EAChC,MAAO,CAAE,sCAAuC,OAAO,CACxD,EACD,CACE,MAAO,CAAC,GAAGO,EAAQ,GAAGC,CAAK,EAC3B,KAAM,uCACN,MAAO,CAAE,sCAAuC,KAAK,CACtD,GAGHC,EAAUN,GAAoD,CAAC,GAAGD,EAAUC,CAAO,EAAG,GAAGG,EAAcH,CAAO,CAAC,EAO/GO,EAAkB,CAAC,CACvB,MAAAC,EAAQ,CAAA,EACR,MAAAN,EAAQ,CAAC,sCAAsC,CAAC,EACvB,KAAiC,CAC1D,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,2BACN,QAAS,CAAE,QAASL,CAAY,EAChC,MAAO,CAAE,8BAA+B,OAAO,CAChD,EACD,GAAIW,EAAM,SAAW,EACjB,CAAA,EACA,CACE,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,mCACN,MAAO,CAAE,8BAA+B,KAAc,CACvD,IAIHC,EAAmB,OAAO,OAAOZ,EAAc,CACnD,QAAS,CAAE,gBAAAU,EAAiB,OAAAD,EAAQ,YAAAR,CAAW,CAChD,CAAA,EAKKY,EAAcD"}
@@ -0,0 +1,26 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ import { TSESLint } from '@typescript-eslint/utils';
3
+ declare const constructors: {
4
+ readonly condition: readonly ["@opetope/runtime", "defineCondition"];
5
+ readonly pipe: readonly ["@opetope/core", "definePipe"];
6
+ readonly port: readonly ["@opetope/core", "definePort"];
7
+ readonly register: readonly ["@opetope/core", "defineRegistry"];
8
+ readonly slot: readonly ["@opetope/react", "defineSlot"];
9
+ };
10
+ type Builder = keyof typeof constructors;
11
+ type Entry = {
12
+ readonly property: TSESTree.Property;
13
+ readonly call: TSESTree.CallExpression;
14
+ readonly builder: Builder;
15
+ readonly target: TSESTree.Identifier;
16
+ readonly value: TSESTree.Expression;
17
+ readonly priority: number;
18
+ readonly enabled: TSESTree.Node | undefined;
19
+ };
20
+ declare function bodyCallback(source: TSESLint.SourceCode, call: TSESTree.CallExpression): {
21
+ readonly fn: TSESTree.ArrowFunctionExpression;
22
+ readonly entries: readonly Entry[];
23
+ } | undefined;
24
+ declare function headerRecord(source: TSESLint.SourceCode, node: TSESTree.Node | undefined): TSESTree.ObjectExpression | undefined;
25
+ declare function recordReplacement(source: TSESLint.SourceCode, call: TSESTree.CallExpression, fn: TSESTree.ArrowFunctionExpression, entries: readonly Entry[]): string | undefined;
26
+ export { bodyCallback, headerRecord, recordReplacement };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as u,TSESLint as b}from"@typescript-eslint/utils";import{findProperty as c,propertyName as s}from"./ast.js";import{variableOf as l,modelBinding as E,localValue as T,isApi as h,definitionOf as O}from"./model-bindings.js";const g={condition:["@opetope/runtime","defineCondition"],pipe:["@opetope/core","definePipe"],port:["@opetope/core","definePort"],register:["@opetope/core","defineRegistry"],slot:["@opetope/react","defineSlot"]};function p(e){if(e?.type!==u.ObjectExpression)return;const n=e.properties.filter(t=>t.type===u.Property&&t.kind==="init"&&!t.method&&s(t)!==void 0),r=n.map(s);return n.length===e.properties.length&&new Set(r).size===r.length?n:void 0}function A(e){return e.type===u.Literal&&typeof e.value=="number"?e.value:void 0}function F(e){if(e===void 0)return 0;if(e.type!==u.UnaryExpression)return A(e);const n=A(e.argument);return e.operator==="-"&&n!==void 0?-n:void 0}function y(e,n){return e.find(r=>s(r)===n)?.value}function k(e){const n=e===void 0?[]:p(e);if(n===void 0||n.some(t=>!["priority","enabled"].includes(s(t))))return;const r=F(y(n,"priority"));if(!(r===void 0||!Number.isFinite(r)))return{priority:r,enabled:y(n,"enabled")}}function P(e){if(e.body.type!==u.BlockStatement)return e.body;if(e.body.body.length!==1)return;const n=e.body.body[0];return n.type===u.ReturnStatement?n.argument??void 0:void 0}function v(e){const n=P(e);return n?.type===u.ObjectExpression?n:void 0}function w(e){const n=e.params[0];return e.params.length===1&&n?.type===u.ObjectPattern?n:void 0}function I(e,n,r){const t=w(n);if(t===void 0||r.type!==u.Identifier)return;const i=l(e,r),o=t.properties.find(f=>f.type===u.Property&&f.value.type===u.Identifier&&l(e,f.value)===i);if(o===void 0)return;const d=s(o);return d!==void 0&&Object.hasOwn(g,d)?d:void 0}function V(e,n){if(["port","condition"].includes(e))return n.type===u.ArrowFunctionExpression;const r=n.type===u.ArrowFunctionExpression?v(n):n;if(r?.type!==u.ObjectExpression)return!1;const t={slot:"Component",pipe:"fold",register:"key",port:"",condition:""}[e];return c(r,t)!==void 0}function D(e){const n=e.value;if(!(n.type!==u.CallExpression||n.optional))return[2,3].includes(n.arguments.length)?n:void 0}function R(e,n){const r=e.arguments[0],t=e.arguments[1],i=e.arguments[2];if(r.type!==u.Identifier||!V(n,t)||["port","condition"].includes(n)&&i!==void 0)return;const o=k(i);return o===void 0?void 0:{target:r,value:t,...o}}function N(e,n,r){const t=D(r);if(t===void 0)return;const i=I(e,n,t.callee);if(i===void 0)return;const o=R(t,i);return o===void 0?void 0:{property:r,call:t,builder:i,...o}}function $(e,n,r){const[t]=n.params;return t?.type!==u.ObjectPattern||t.properties.some(i=>!Object.hasOwn(g,s(i)??""))?!1:t.properties.every(i=>i.type!==u.Property||i.value.type!==u.Identifier?!1:l(e,i.value)?.references.every(d=>r.some(f=>f.call.callee===d.identifier))??!1)}function B(e){if(!(e?.type!==u.MemberExpression||e.computed))return e.property.type===u.Identifier&&e.property.name==="body"?e.object:void 0}function L(e){return e?.type===u.ArrowFunctionExpression&&!e.async&&!e.typeParameters?e:void 0}function z(e,n){const r=O(e,n);if(r?.type!==b.Scope.DefinitionType.Variable)return n;if(!(r.parent.kind!=="const"||r.node.id.typeAnnotation))return r.node.init??void 0}function a(e,n,r=new Set){if(n.type!==u.Identifier)return n;if(r.has(n))return;r.add(n);const t=z(e,n);return t===void 0||t===n?t:a(e,t,r)}function _(e,n,r){if(r===void 0||!h(e,r,"@opetope/runtime","defineFeature",!0))return;const t=n.arguments[1];if(t?.type!==u.ObjectExpression||p(t)===void 0)return;const i=c(t,"provides")?.value;return L(i)}function S(e,n,r=!1){const t=r?a:T,i=B(t(e,n.callee));return i===void 0?void 0:_(e,n,t(e,i))}function M(e,n){const r=S(e,n);if(r===void 0)return;const t=p(v(r));if(t===void 0||t.length===0)return;const i=t.map(d=>N(e,r,d));if(i.some(d=>d===void 0))return;const o=i;return $(e,r,o)?{fn:r,entries:o}:void 0}function j(e,n,r,t){const i=a(e,n);return i!==void 0&&h(e,i,r,t,!0)}function U(e){return e?.type===u.CallExpression&&!e.typeArguments}function W(e,n){if(n===void 0)return;const r=a(e,n);if(!U(r)||!j(e,r.callee,"@opetope/runtime","defineFeature"))return;const t=r.arguments[1];return t?.type===u.ObjectExpression&&p(t)!==void 0?t:void 0}function C(e,n){const r=W(e,n);if(r===void 0||c(r,"body")===void 0)return;const t=c(r,"provides")?.value;return t?.type===u.ObjectExpression&&p(t)!==void 0?t:void 0}function Y(e){const n=p(e),r=n===void 0?e:y(n,"target"),t=n===void 0?0:F(y(n,"priority"));return r?.type===u.Identifier?{target:r,priority:t}:void 0}function q(e,n,r){const t=Y(r);if(t===void 0||t.priority!==n.priority||E(e,t.target)!==E(e,n.target))return!1;const i=a(e,t.target),[o,d]=g[n.builder];return i!==a(e,n.target)?!1:i?.type===u.CallExpression&&j(e,i.callee,o,d)}function G(e,n,r){const t=O(e,n);return t?.type===b.Scope.DefinitionType.FunctionName?!l(e,n)?.references.some(i=>i.isWrite()):t?.type===b.Scope.DefinitionType.Variable&&t.parent.kind==="const"&&t.node.range[1]<r}function x(e,n,r){return[u.Literal,u.ArrowFunctionExpression,u.FunctionExpression].includes(n.type)?!0:n.type===u.Identifier?G(e,n,r):p(n)?.every(i=>x(e,i.value,r))??!1}function H(e){return e.type!==u.ArrowFunctionExpression?e:e.returnType||e.typeParameters?void 0:v(e)}function J(e){if(e.call.typeArguments)return!1;if(["port","condition"].includes(e.builder))return!0;const n=H(e.value),r={slot:["Component","props","models"],pipe:["fold"],register:["key","value"],port:[],condition:[]}[e.builder];return p(n)?.every(t=>r.includes(s(t)))??!1}function K(e,n,r){return n.typeArguments||e.getCommentsInside(r).length>0||S(e,n,!0)===void 0||w(r)?.typeAnnotation?!1:!e.getAncestors(n).some(t=>[u.ArrowFunctionExpression,u.FunctionExpression,u.FunctionDeclaration].includes(t.type))}function Q(e,n,r,t){if(!K(e,n,r))return;const i=p(C(e,n.arguments[0]));if(!(i===void 0||i.length!==t.length||!t.every(d=>{const f=i.find(m=>s(m)===s(d.property));return f!==void 0&&J(d)&&q(e,d,f.value)}))&&t.every(d=>x(e,d.value,n.range[0])&&(d.enabled===void 0||x(e,d.enabled,n.range[0]))))return`{ ${t.map(d=>{const f=e.getText(d.value),m=d.enabled===void 0?f:`[${f}, { enabled: ${e.getText(d.enabled)} }]`;return`${e.getText(d.property.key)}: ${m}`}).join(", ")} }`}export{M as bodyCallback,C as headerRecord,Q as recordReplacement};
2
+ //# sourceMappingURL=lazy-body-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-body-migration.js","sources":["../src/lazy-body-migration.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils';\n\nimport { findProperty, propertyName } from './ast';\nimport { definitionOf, isApi, localValue, modelBinding, variableOf } from './model-bindings';\n\nconst constructors = {\n condition: ['@opetope/runtime', 'defineCondition'],\n pipe: ['@opetope/core', 'definePipe'],\n port: ['@opetope/core', 'definePort'],\n register: ['@opetope/core', 'defineRegistry'],\n slot: ['@opetope/react', 'defineSlot'],\n} as const;\ntype Builder = keyof typeof constructors;\ntype Entry = {\n readonly property: TSESTree.Property;\n readonly call: TSESTree.CallExpression;\n readonly builder: Builder;\n readonly target: TSESTree.Identifier;\n readonly value: TSESTree.Expression;\n readonly priority: number;\n readonly enabled: TSESTree.Node | undefined;\n};\n\nfunction properties(node: TSESTree.Node | undefined): readonly TSESTree.Property[] | undefined {\n if (node?.type !== AST_NODE_TYPES.ObjectExpression) return undefined;\n const fields = node.properties.filter(\n (field): field is TSESTree.Property =>\n field.type === AST_NODE_TYPES.Property &&\n field.kind === 'init' &&\n !field.method &&\n propertyName(field) !== undefined,\n );\n const names = fields.map(propertyName);\n return fields.length === node.properties.length && new Set(names).size === names.length ? fields : undefined;\n}\n\nfunction literalNumber(node: TSESTree.Node): number | undefined {\n return node.type === AST_NODE_TYPES.Literal && typeof node.value === 'number' ? node.value : undefined;\n}\nfunction numeric(node: TSESTree.Node | undefined): number | undefined {\n if (node === undefined) return 0;\n if (node.type !== AST_NODE_TYPES.UnaryExpression) return literalNumber(node);\n const value = literalNumber(node.argument);\n return node.operator === '-' && value !== undefined ? -value : undefined;\n}\nfunction fieldValue(fields: readonly TSESTree.Property[], name: string): TSESTree.Node | undefined {\n return fields.find(field => propertyName(field) === name)?.value;\n}\n\nfunction modifiers(node: TSESTree.Node | undefined): Pick<Entry, 'priority' | 'enabled'> | undefined {\n const fields = node === undefined ? [] : properties(node);\n if (fields === undefined) return undefined;\n if (fields.some(field => !['priority', 'enabled'].includes(propertyName(field)!))) return undefined;\n const priority = numeric(fieldValue(fields, 'priority'));\n if (priority === undefined || !Number.isFinite(priority)) return undefined;\n return { priority, enabled: fieldValue(fields, 'enabled') };\n}\n\nfunction returnedValue(fn: TSESTree.ArrowFunctionExpression): TSESTree.Node | undefined {\n if (fn.body.type !== AST_NODE_TYPES.BlockStatement) return fn.body;\n if (fn.body.body.length !== 1) return undefined;\n const first = fn.body.body[0]!;\n return first.type === AST_NODE_TYPES.ReturnStatement ? (first.argument ?? undefined) : undefined;\n}\nfunction returnedRecord(fn: TSESTree.ArrowFunctionExpression): TSESTree.ObjectExpression | undefined {\n const value = returnedValue(fn);\n return value?.type === AST_NODE_TYPES.ObjectExpression ? value : undefined;\n}\nfunction parameterOf(fn: TSESTree.ArrowFunctionExpression): TSESTree.ObjectPattern | undefined {\n const parameter = fn.params[0];\n return fn.params.length === 1 && parameter?.type === AST_NODE_TYPES.ObjectPattern ? parameter : undefined;\n}\n\nfunction builderOf(\n source: TSESLint.SourceCode,\n fn: TSESTree.ArrowFunctionExpression,\n callee: TSESTree.Node,\n): Builder | undefined {\n const parameter = parameterOf(fn);\n if (parameter === undefined || callee.type !== AST_NODE_TYPES.Identifier) return undefined;\n const binding = variableOf(source, callee);\n const field = parameter.properties.find(\n field =>\n field.type === AST_NODE_TYPES.Property &&\n field.value.type === AST_NODE_TYPES.Identifier &&\n variableOf(source, field.value) === binding,\n );\n if (field === undefined) return undefined;\n const name = propertyName(field);\n return name !== undefined && Object.hasOwn(constructors, name) ? (name as Builder) : undefined;\n}\n\nfunction recordValue(builder: Builder, value: TSESTree.Node): value is TSESTree.Expression {\n if (['port', 'condition'].includes(builder)) return value.type === AST_NODE_TYPES.ArrowFunctionExpression;\n const record = value.type === AST_NODE_TYPES.ArrowFunctionExpression ? returnedRecord(value) : value;\n if (record?.type !== AST_NODE_TYPES.ObjectExpression) return false;\n const key = { slot: 'Component', pipe: 'fold', register: 'key', port: '', condition: '' }[builder];\n return findProperty(record, key) !== undefined;\n}\nfunction entryCall(property: TSESTree.Property): TSESTree.CallExpression | undefined {\n const call = property.value;\n if (call.type !== AST_NODE_TYPES.CallExpression || call.optional) return undefined;\n return [2, 3].includes(call.arguments.length) ? call : undefined;\n}\nfunction entryArguments(call: TSESTree.CallExpression, builder: Builder) {\n const target = call.arguments[0]!;\n const value = call.arguments[1]!;\n const options = call.arguments[2];\n if (target.type !== AST_NODE_TYPES.Identifier) return undefined;\n if (!recordValue(builder, value)) return undefined;\n if (['port', 'condition'].includes(builder) && options !== undefined) return undefined;\n const parsed = modifiers(options);\n return parsed === undefined ? undefined : { target, value, ...parsed };\n}\n\nfunction entryOf(\n source: TSESLint.SourceCode,\n fn: TSESTree.ArrowFunctionExpression,\n property: TSESTree.Property,\n): Entry | undefined {\n const call = entryCall(property);\n if (call === undefined) return undefined;\n const builder = builderOf(source, fn, call.callee);\n if (builder === undefined) return undefined;\n const args = entryArguments(call, builder);\n return args === undefined ? undefined : { property, call, builder, ...args };\n}\n\nfunction onlyBuilderCalls(\n source: TSESLint.SourceCode,\n fn: TSESTree.ArrowFunctionExpression,\n entries: readonly Entry[],\n): boolean {\n const [parameter] = fn.params;\n if (parameter?.type !== AST_NODE_TYPES.ObjectPattern) return false;\n if (parameter.properties.some(field => !Object.hasOwn(constructors, propertyName(field) ?? ''))) return false;\n return parameter.properties.every(field => {\n if (field.type !== AST_NODE_TYPES.Property || field.value.type !== AST_NODE_TYPES.Identifier) return false;\n const variable = variableOf(source, field.value);\n return (\n variable?.references.every(reference => entries.some(entry => entry.call.callee === reference.identifier)) ??\n false\n );\n });\n}\n\nfunction bodyOwner(node: TSESTree.Node | undefined): TSESTree.Node | undefined {\n if (node?.type !== AST_NODE_TYPES.MemberExpression || node.computed) return undefined;\n return node.property.type === AST_NODE_TYPES.Identifier && node.property.name === 'body' ? node.object : undefined;\n}\nfunction synchronousArrow(node: TSESTree.Node | undefined): TSESTree.ArrowFunctionExpression | undefined {\n return node?.type === AST_NODE_TYPES.ArrowFunctionExpression && !node.async && !node.typeParameters\n ? node\n : undefined;\n}\nfunction inferredInitializer(source: TSESLint.SourceCode, node: TSESTree.Identifier): TSESTree.Node | undefined {\n const definition = definitionOf(source, node);\n if (definition?.type !== TSESLint.Scope.DefinitionType.Variable) return node;\n if (definition.parent.kind !== 'const' || Boolean(definition.node.id.typeAnnotation)) return undefined;\n return definition.node.init ?? undefined;\n}\n\nfunction stableLocal(\n source: TSESLint.SourceCode,\n node: TSESTree.Node,\n seen = new Set<TSESTree.Node>(),\n): TSESTree.Node | undefined {\n if (node.type !== AST_NODE_TYPES.Identifier) return node;\n if (seen.has(node)) return undefined;\n seen.add(node);\n const initial = inferredInitializer(source, node);\n if (initial === undefined || initial === node) return initial;\n return stableLocal(source, initial, seen);\n}\nfunction bodyFunctionOf(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n owner: TSESTree.Node | undefined,\n): TSESTree.ArrowFunctionExpression | undefined {\n if (owner === undefined || !isApi(source, owner, '@opetope/runtime', 'defineFeature', true)) return undefined;\n const sections = call.arguments[1];\n if (sections?.type !== AST_NODE_TYPES.ObjectExpression || properties(sections) === undefined) return undefined;\n const fn = findProperty(sections, 'provides')?.value;\n return synchronousArrow(fn);\n}\nfunction bodyFunction(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n stable = false,\n): TSESTree.ArrowFunctionExpression | undefined {\n const resolve = stable ? stableLocal : localValue;\n const owner = bodyOwner(resolve(source, call.callee));\n return owner === undefined ? undefined : bodyFunctionOf(source, call, resolve(source, owner));\n}\n\nfunction bodyCallback(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n): { readonly fn: TSESTree.ArrowFunctionExpression; readonly entries: readonly Entry[] } | undefined {\n const fn = bodyFunction(source, call);\n if (fn === undefined) return undefined;\n const fields = properties(returnedRecord(fn));\n if (fields === undefined || fields.length === 0) return undefined;\n const entries = fields.map(field => entryOf(source, fn, field));\n if (entries.some(entry => entry === undefined)) return undefined;\n const known = entries as Entry[];\n return onlyBuilderCalls(source, fn, known) ? { fn, entries: known } : undefined;\n}\nfunction stableApi(source: TSESLint.SourceCode, callee: TSESTree.Node, module: string, name: string): boolean {\n const resolved = stableLocal(source, callee);\n return resolved !== undefined && isApi(source, resolved, module, name, true);\n}\nfunction inferredCall(node: TSESTree.Node | undefined): node is TSESTree.CallExpression {\n // The Oxlint bridge represents absent AST annotations as null; ESLint omits them.\n return node?.type === AST_NODE_TYPES.CallExpression && !node.typeArguments;\n}\nfunction headerSections(\n source: TSESLint.SourceCode,\n node: TSESTree.Node | undefined,\n): TSESTree.ObjectExpression | undefined {\n if (node === undefined) return undefined;\n const header = stableLocal(source, node);\n if (!inferredCall(header)) return undefined;\n if (!stableApi(source, header.callee, '@opetope/runtime', 'defineFeature')) return undefined;\n const sections = header.arguments[1];\n return sections?.type === AST_NODE_TYPES.ObjectExpression && properties(sections) !== undefined\n ? sections\n : undefined;\n}\n\nfunction headerRecord(\n source: TSESLint.SourceCode,\n node: TSESTree.Node | undefined,\n): TSESTree.ObjectExpression | undefined {\n const sections = headerSections(source, node);\n if (sections === undefined || findProperty(sections, 'body') === undefined) return undefined;\n const provided = findProperty(sections, 'provides')?.value;\n return provided?.type === AST_NODE_TYPES.ObjectExpression && properties(provided) !== undefined\n ? provided\n : undefined;\n}\nfunction declaredTarget(declared: TSESTree.Node) {\n const fields = properties(declared);\n const target = fields === undefined ? declared : fieldValue(fields, 'target');\n const priority = fields === undefined ? 0 : numeric(fieldValue(fields, 'priority'));\n return target?.type === AST_NODE_TYPES.Identifier ? { target, priority } : undefined;\n}\n\nfunction matchingTarget(source: TSESLint.SourceCode, entry: Entry, declared: TSESTree.Node): boolean {\n const metadata = declaredTarget(declared);\n if (metadata === undefined || metadata.priority !== entry.priority) return false;\n if (modelBinding(source, metadata.target) !== modelBinding(source, entry.target)) return false;\n const declaration = stableLocal(source, metadata.target);\n const [module, name] = constructors[entry.builder];\n if (declaration !== stableLocal(source, entry.target)) return false;\n return declaration?.type === AST_NODE_TYPES.CallExpression && stableApi(source, declaration.callee, module, name);\n}\nfunction stableReference(source: TSESLint.SourceCode, node: TSESTree.Identifier, at: number): boolean {\n const definition = definitionOf(source, node);\n if (definition?.type === TSESLint.Scope.DefinitionType.FunctionName)\n return !variableOf(source, node)?.references.some(reference => reference.isWrite());\n return (\n definition?.type === TSESLint.Scope.DefinitionType.Variable &&\n definition.parent.kind === 'const' &&\n definition.node.range[1] < at\n );\n}\n\nfunction pureValue(source: TSESLint.SourceCode, node: TSESTree.Node, at: number): boolean {\n if (\n [AST_NODE_TYPES.Literal, AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression].includes(\n node.type,\n )\n )\n return true;\n if (node.type === AST_NODE_TYPES.Identifier) return stableReference(source, node, at);\n const fields = properties(node);\n return fields?.every(field => pureValue(source, field.value, at)) ?? false;\n}\n\nfunction descriptorOf(value: TSESTree.Expression): TSESTree.Node | undefined {\n if (value.type !== AST_NODE_TYPES.ArrowFunctionExpression) return value;\n return value.returnType || value.typeParameters ? undefined : returnedRecord(value);\n}\nfunction exactDescriptor(entry: Entry): boolean {\n if (entry.call.typeArguments) return false;\n if (['port', 'condition'].includes(entry.builder)) return true;\n const record = descriptorOf(entry.value);\n const allowed: readonly string[] = {\n slot: ['Component', 'props', 'models'],\n pipe: ['fold'],\n register: ['key', 'value'],\n port: [],\n condition: [],\n }[entry.builder];\n return properties(record)?.every(field => allowed.includes(propertyName(field)!)) ?? false;\n}\n\nfunction movableCallback(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n fn: TSESTree.ArrowFunctionExpression,\n): boolean {\n if (call.typeArguments || source.getCommentsInside(fn).length > 0 || bodyFunction(source, call, true) === undefined)\n return false;\n if (parameterOf(fn)?.typeAnnotation) return false;\n return !source\n .getAncestors(call)\n .some(node =>\n [\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.FunctionExpression,\n AST_NODE_TYPES.FunctionDeclaration,\n ].includes(node.type),\n );\n}\nfunction recordReplacement(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n fn: TSESTree.ArrowFunctionExpression,\n entries: readonly Entry[],\n): string | undefined {\n if (!movableCallback(source, call, fn)) return undefined;\n const fields = properties(headerRecord(source, call.arguments[0]));\n if (fields === undefined || fields.length !== entries.length) return undefined;\n const matches = entries.every(entry => {\n const declared = fields.find(field => propertyName(field) === propertyName(entry.property));\n return declared !== undefined && exactDescriptor(entry) && matchingTarget(source, entry, declared.value);\n });\n if (!matches) return undefined;\n if (\n !entries.every(\n entry =>\n pureValue(source, entry.value, call.range[0]) &&\n (entry.enabled === undefined || pureValue(source, entry.enabled, call.range[0])),\n )\n )\n return undefined;\n return `{ ${entries\n .map(entry => {\n const value = source.getText(entry.value);\n const implementation =\n entry.enabled === undefined ? value : `[${value}, { enabled: ${source.getText(entry.enabled)} }]`;\n return `${source.getText(entry.property.key)}: ${implementation}`;\n })\n .join(', ')} }`;\n}\n\nexport { bodyCallback, headerRecord, recordReplacement };\n"],"names":["constructors","properties","node","AST_NODE_TYPES","fields","field","propertyName","names","literalNumber","numeric","value","fieldValue","name","modifiers","priority","returnedValue","fn","first","returnedRecord","parameterOf","parameter","builderOf","source","callee","binding","variableOf","recordValue","builder","record","key","findProperty","entryCall","property","call","entryArguments","target","options","parsed","entryOf","args","onlyBuilderCalls","entries","reference","entry","bodyOwner","synchronousArrow","inferredInitializer","definition","definitionOf","TSESLint","stableLocal","seen","initial","bodyFunctionOf","owner","isApi","sections","bodyFunction","stable","resolve","localValue","bodyCallback","known","stableApi","module","resolved","inferredCall","headerSections","header","headerRecord","provided","declaredTarget","declared","matchingTarget","metadata","modelBinding","declaration","stableReference","at","pureValue","descriptorOf","exactDescriptor","allowed","movableCallback","recordReplacement","implementation"],"mappings":"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"}
@@ -18,14 +18,14 @@ declare function isResourceHook(source: TSESLint.SourceCode, node: TSESTree.Node
18
18
  */
19
19
  declare function isSurfaceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean;
20
20
  /**
21
- * D375: `activity` left the `loading` state and stayed on the `ready` one, so a read of it is retired only where a
22
- * narrowing proves which branch it is about. Both halves are asked: the expression is a `ResourceState` of this
23
- * library, and the read stands inside a guard written about that same expression.
21
+ * D462: `activity` is gone from every member of `ResourceState`, so a read of it is retired wherever the expression
22
+ * is one of ours — there is no branch left in which the field survives. D375 had retired it on `loading` alone, so
23
+ * the rule had to prove which branch a read stood in; that machinery is gone with the half-retirement it served.
24
24
  */
25
- declare function isLoadingActivity(source: TSESLint.SourceCode, node: TSESTree.MemberExpression): boolean;
25
+ declare function isStateActivity(source: TSESLint.SourceCode, node: TSESTree.MemberExpression): boolean;
26
26
  /**
27
27
  * D411: the settled answer a consumer keeps — `.outcome` read on a `useCommand`, or the name it was destructured
28
28
  * into. `status` is a common word, so the rule asks the symbol: nothing but this object is reported.
29
29
  */
30
30
  declare function isCommandOutcome(source: TSESLint.SourceCode, node: TSESTree.Node): boolean;
31
- export { isCommandOutcome, isLoadingActivity, isResourceHook, isResourceValue, isSurfaceValue };
31
+ export { isCommandOutcome, isResourceHook, isResourceValue, isStateActivity, isSurfaceValue };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as r,TSESLint as b}from"@typescript-eslint/utils";import{within as g,propertyName as x}from"./ast.js";import{methodPath as S,isContextFunction as C}from"./declaration-ingress.js";import{localValue as p,isApi as a,unwrap as u,definitionOf as h}from"./model-bindings.js";const v=new Set(["resource.live","resource.load"]),I="loading";function c(e,t){const n=p(e,t);if(n.type!==r.CallExpression)return!1;const i=S(e,n.callee);return i!==void 0&&v.has(i.method)&&C(e,i.fn)}function s(e,t){const n=p(e,t);return n.type===r.CallExpression&&a(e,n.callee,"@opetope/react","useResource",!0)}function y(e,t){const n=p(e,t);return n.type===r.CallExpression&&a(e,n.callee,"@opetope/react","useCommand",!0)}function O(e,t){const n=p(e,t);return n.type===r.CallExpression&&a(e,n.callee,"@opetope/navigation/react","defineSurface",!0)}function m(e,t){if(t.type!==r.MemberExpression||t.computed||t.property.type!==r.Identifier||t.property.name!=="state")return!1;const n=u(t.object);return c(e,n)||s(e,n)}function R(e,t){return t.type!==r.MemberExpression||t.computed||t.property.type!==r.Identifier||t.property.name!=="getSnapshot"?!1:m(e,u(t.object))}function d(e,t,n){const i=h(e,t);if(i?.type!==b.Scope.DefinitionType.Variable)return;const{init:o}=i.node,f=i.name.parent;if(!(o===null||f.type!==r.Property)&&!(x(f)!==n||f.parent!==i.node.id))return o}function T(e,t){if(t.type===r.Identifier){const i=d(e,t,"state");if(i!==void 0)return s(e,i)}const n=p(e,t);return n.type===r.CallExpression?R(e,n.callee):m(e,n)}function j(e,t,n){return t.type!==r.MemberExpression||t.computed||t.property.type!==r.Identifier||t.property.name!=="kind"?!1:e.getText(t.object)===n}function E(e,t,n,i){return n.type===r.Literal&&n.value===I&&j(e,t,i)}function l(e,t,n){return t.type===r.LogicalExpression&&t.operator==="&&"?l(e,t.left,n)||l(e,t.right,n):t.type!==r.BinaryExpression||t.operator!=="==="?!1:E(e,t.left,t.right,n)||E(e,t.right,t.left,n)}function L(e){if(e.type===r.IfStatement)return{body:e.consequent,test:e.test};if(e.type===r.ConditionalExpression)return{body:e.consequent,test:e.test};if(e.type===r.LogicalExpression&&e.operator==="&&")return{body:e.right,test:e.left}}function k(e,t){if(!T(e,t.object))return!1;const n=e.getText(t.object);return e.getAncestors(t).some(i=>{const o=L(i);return o!==void 0&&g(t,o.body)&&l(e,o.test,n)})}function A(e,t){if(t.type===r.Identifier){const i=d(e,t,"outcome");if(i!==void 0)return y(e,i)}const n=p(e,t);return n.type!==r.MemberExpression||n.computed||n.property.type!==r.Identifier||n.property.name!=="outcome"?!1:y(e,u(n.object))}export{A as isCommandOutcome,k as isLoadingActivity,s as isResourceHook,c as isResourceValue,O as isSurfaceValue};
1
+ import{AST_NODE_TYPES as i,TSESLint as y}from"@typescript-eslint/utils";import{propertyName as S}from"./ast.js";import{methodPath as E,isContextFunction as C}from"./declaration-ingress.js";import{localValue as o,isApi as a,unwrap as u,definitionOf as b}from"./model-bindings.js";const x=new Set(["resource.live","resource.load"]);function c(e,t){const n=o(e,t);if(n.type!==i.CallExpression)return!1;const r=E(e,n.callee);return r!==void 0&&x.has(r.method)&&C(e,r.fn)}function f(e,t){const n=o(e,t);return n.type===i.CallExpression&&a(e,n.callee,"@opetope/react","useResource",!0)}function l(e,t){const n=o(e,t);return n.type===i.CallExpression&&a(e,n.callee,"@opetope/react","useCommand",!0)}function v(e,t){const n=o(e,t);return n.type===i.CallExpression&&a(e,n.callee,"@opetope/navigation/react","defineSurface",!0)}function m(e,t){if(t.type!==i.MemberExpression||t.computed||t.property.type!==i.Identifier||t.property.name!=="state")return!1;const n=u(t.object);return c(e,n)||f(e,n)}function R(e,t){return t.type!==i.MemberExpression||t.computed||t.property.type!==i.Identifier||t.property.name!=="getSnapshot"?!1:m(e,u(t.object))}function d(e,t,n){const r=b(e,t);if(r?.type!==y.Scope.DefinitionType.Variable)return;const{init:s}=r.node,p=r.name.parent;if(!(s===null||p.type!==i.Property)&&!(S(p)!==n||p.parent!==r.node.id))return s}function h(e,t){if(t.type===i.Identifier){const r=d(e,t,"state");if(r!==void 0)return f(e,r)}const n=o(e,t);return n.type===i.CallExpression?R(e,n.callee):m(e,n)}function I(e,t){return h(e,t.object)}function j(e,t){if(t.type===i.Identifier){const r=d(e,t,"outcome");if(r!==void 0)return l(e,r)}const n=o(e,t);return n.type!==i.MemberExpression||n.computed||n.property.type!==i.Identifier||n.property.name!=="outcome"?!1:l(e,u(n.object))}export{j as isCommandOutcome,f as isResourceHook,c as isResourceValue,I as isStateActivity,v as isSurfaceValue};
2
2
  //# sourceMappingURL=retired-vocabulary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"retired-vocabulary.js","sources":["../src/retired-vocabulary.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils';\n\nimport { propertyName, within } from './ast';\nimport { isContextFunction, methodPath } from './declaration-ingress';\nimport { definitionOf, isApi, localValue, unwrap } from './model-bindings';\n\n/** D349: the two factories of the Resource family. Both answer the object whose verbs a migration renames. */\nconst RESOURCE_FACTORIES = new Set(['resource.live', 'resource.load']);\n/** D375: the one kind of `ResourceState` that lost `activity`; a `ready` one still carries the record whole. */\nconst LOADING = 'loading';\n\n/** A narrowing as it is written: the test that proves the kind, and the half of the code it protects. */\ntype Guard = { readonly body: TSESTree.Node; readonly test: TSESTree.Node };\n\n/**\n * A value this module proved is a Resource of this library: the `resource.load` or `resource.live` that declared it,\n * read past the name it was given. The shape decides first and the resolution second, so a call that cannot be a\n * declaration never pays for the context it would have been declared on (D323).\n */\nfunction isResourceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.CallExpression) return false;\n\n const named = methodPath(source, value.callee);\n\n return named !== undefined && RESOURCE_FACTORIES.has(named.method) && isContextFunction(source, named.fn);\n}\n\n/**\n * `useResource(book)`: the record of hooks, under whatever local name this file gave the import. The module is\n * `@opetope/react` exactly — a host's own `./hooks` must not pass for ours, which is what `exact` asks for (D309).\n */\nfunction isResourceHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useResource', true)\n );\n}\n\n/**\n * D411: `useCommand(save)` — the one consumer of a command, under whatever local name this file gave the import.\n * The module is `@opetope/react` exactly, for the reason `useResource` is asked exactly (D309).\n */\nfunction isCommandHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useCommand', true)\n );\n}\n\n/**\n * D411, D415: the surface `defineSurface` answers, under whatever local name this file gave the import. The module\n * is `@opetope/navigation/react` exactly, for the reason `useResource` is asked exactly (D309): `policy` is a word\n * a host writes about its own objects all day, so the rule asks the symbol and never the spelling.\n */\nfunction isSurfaceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression &&\n isApi(source, value.callee, '@opetope/navigation/react', 'defineSurface', true)\n );\n}\n\n/** `.state` read on a Resource of this library or on the record `useResource` answers. */\nfunction isStateMember(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'state') return false;\n\n const object = unwrap(node.object);\n\n return isResourceValue(source, object) || isResourceHook(source, object);\n}\n\n/** `book.state.getSnapshot()`: the state as a model reads it. */\nfunction isSnapshotRead(source: TSESLint.SourceCode, callee: TSESTree.Node): boolean {\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return false;\n\n return isStateMember(source, unwrap(callee.object));\n}\n\n/**\n * `const { state } = useResource(book)`: the value a name was destructured out of, under the key it was taken by.\n * `localValue` answers the whole initializer for such a name, which is the record and not the member, so the key is\n * read here instead — the way a context member is read off the pattern that bound it.\n */\nfunction takenFrom(source: TSESLint.SourceCode, node: TSESTree.Identifier, key: string): TSESTree.Node | undefined {\n const definition = definitionOf(source, node);\n\n if (definition?.type !== TSESLint.Scope.DefinitionType.Variable) return undefined;\n\n const { init } = definition.node;\n const property = definition.name.parent;\n\n if (init === null || property.type !== AST_NODE_TYPES.Property) return undefined;\n\n if (propertyName(property) !== key || property.parent !== definition.node.id) return undefined;\n\n return init;\n}\n\n/** The `ResourceState` a name holds: the member itself, one value read out of it, or the key it was taken from. */\nfunction isResourceState(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'state');\n\n if (taken !== undefined) return isResourceHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n return value.type === AST_NODE_TYPES.CallExpression\n ? isSnapshotRead(source, value.callee)\n : isStateMember(source, value);\n}\n\n/** `state.kind` read off the same expression the `activity` was read off, compared as the author wrote it. */\nfunction isKindOf(source: TSESLint.SourceCode, node: TSESTree.Node, text: string): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'kind') return false;\n\n return source.getText(node.object) === text;\n}\n\nfunction isLoadingTest(source: TSESLint.SourceCode, kind: TSESTree.Node, word: TSESTree.Node, text: string): boolean {\n return word.type === AST_NODE_TYPES.Literal && word.value === LOADING && isKindOf(source, kind, text);\n}\n\n/** `state.kind === 'loading'` written about this very expression, through the `&&` chain a guard may be built of. */\nfunction narrows(source: TSESLint.SourceCode, test: TSESTree.Node, text: string): boolean {\n if (test.type === AST_NODE_TYPES.LogicalExpression && test.operator === '&&') {\n return narrows(source, test.left, text) || narrows(source, test.right, text);\n }\n\n if (test.type !== AST_NODE_TYPES.BinaryExpression || test.operator !== '===') return false;\n\n return isLoadingTest(source, test.left, test.right, text) || isLoadingTest(source, test.right, test.left, text);\n}\n\n/** The three shapes a narrowing is written in, each with the half of the code it protects. */\nfunction guardOf(node: TSESTree.Node): Guard | undefined {\n if (node.type === AST_NODE_TYPES.IfStatement) return { body: node.consequent, test: node.test };\n\n if (node.type === AST_NODE_TYPES.ConditionalExpression) return { body: node.consequent, test: node.test };\n\n if (node.type === AST_NODE_TYPES.LogicalExpression && node.operator === '&&') {\n return { body: node.right, test: node.left };\n }\n\n return undefined;\n}\n\n/**\n * D375: `activity` left the `loading` state and stayed on the `ready` one, so a read of it is retired only where a\n * narrowing proves which branch it is about. Both halves are asked: the expression is a `ResourceState` of this\n * library, and the read stands inside a guard written about that same expression.\n */\nfunction isLoadingActivity(source: TSESLint.SourceCode, node: TSESTree.MemberExpression): boolean {\n if (!isResourceState(source, node.object)) return false;\n\n const text = source.getText(node.object);\n\n return source.getAncestors(node).some(ancestor => {\n const guard = guardOf(ancestor);\n\n return guard !== undefined && within(node, guard.body) && narrows(source, guard.test, text);\n });\n}\n\n/**\n * D411: the settled answer a consumer keeps — `.outcome` read on a `useCommand`, or the name it was destructured\n * into. `status` is a common word, so the rule asks the symbol: nothing but this object is reported.\n */\nfunction isCommandOutcome(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'outcome');\n\n if (taken !== undefined) return isCommandHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.MemberExpression || value.computed) return false;\n\n if (value.property.type !== AST_NODE_TYPES.Identifier || value.property.name !== 'outcome') return false;\n\n return isCommandHook(source, unwrap(value.object));\n}\n\nexport { isCommandOutcome, isLoadingActivity, isResourceHook, isResourceValue, isSurfaceValue };\n"],"names":["RESOURCE_FACTORIES","LOADING","isResourceValue","source","node","value","localValue","AST_NODE_TYPES","named","methodPath","isContextFunction","isResourceHook","isApi","isCommandHook","isSurfaceValue","isStateMember","object","unwrap","isSnapshotRead","callee","takenFrom","key","definition","definitionOf","TSESLint","init","property","propertyName","isResourceState","taken","isKindOf","text","isLoadingTest","kind","word","narrows","test","guardOf","isLoadingActivity","ancestor","guard","within","isCommandOutcome"],"mappings":"mSAQA,MAAMA,EAAqB,IAAI,IAAI,CAAC,gBAAiB,eAAe,CAAC,EAE/DC,EAAU,UAUhB,SAASC,EAAgBC,EAA6BC,EAAmB,CACvE,MAAMC,EAAQC,EAAWH,EAAQC,CAAI,EAErC,GAAIC,EAAM,OAASE,EAAe,eAAgB,MAAO,GAEzD,MAAMC,EAAQC,EAAWN,EAAQE,EAAM,MAAM,EAE7C,OAAOG,IAAU,QAAaR,EAAmB,IAAIQ,EAAM,MAAM,GAAKE,EAAkBP,EAAQK,EAAM,EAAE,CAC1G,CAMA,SAASG,EAAeR,EAA6BC,EAAmB,CACtE,MAAMC,EAAQC,EAAWH,EAAQC,CAAI,EAErC,OACEC,EAAM,OAASE,EAAe,gBAAkBK,EAAMT,EAAQE,EAAM,OAAQ,iBAAkB,cAAe,EAAI,CAErH,CAMA,SAASQ,EAAcV,EAA6BC,EAAmB,CACrE,MAAMC,EAAQC,EAAWH,EAAQC,CAAI,EAErC,OACEC,EAAM,OAASE,EAAe,gBAAkBK,EAAMT,EAAQE,EAAM,OAAQ,iBAAkB,aAAc,EAAI,CAEpH,CAOA,SAASS,EAAeX,EAA6BC,EAAmB,CACtE,MAAMC,EAAQC,EAAWH,EAAQC,CAAI,EAErC,OACEC,EAAM,OAASE,EAAe,gBAC9BK,EAAMT,EAAQE,EAAM,OAAQ,4BAA6B,gBAAiB,EAAI,CAElF,CAGA,SAASU,EAAcZ,EAA6BC,EAAmB,CAGrE,GAFIA,EAAK,OAASG,EAAe,kBAAoBH,EAAK,UAEtDA,EAAK,SAAS,OAASG,EAAe,YAAcH,EAAK,SAAS,OAAS,QAAS,MAAO,GAE/F,MAAMY,EAASC,EAAOb,EAAK,MAAM,EAEjC,OAAOF,EAAgBC,EAAQa,CAAM,GAAKL,EAAeR,EAAQa,CAAM,CACzE,CAGA,SAASE,EAAef,EAA6BgB,EAAqB,CAGxE,OAFIA,EAAO,OAASZ,EAAe,kBAAoBY,EAAO,UAE1DA,EAAO,SAAS,OAASZ,EAAe,YAAcY,EAAO,SAAS,OAAS,cAAsB,GAElGJ,EAAcZ,EAAQc,EAAOE,EAAO,MAAM,CAAC,CACpD,CAOA,SAASC,EAAUjB,EAA6BC,EAA2BiB,EAAW,CACpF,MAAMC,EAAaC,EAAapB,EAAQC,CAAI,EAE5C,GAAIkB,GAAY,OAASE,EAAS,MAAM,eAAe,SAAU,OAEjE,KAAM,CAAE,KAAAC,GAASH,EAAW,KACtBI,EAAWJ,EAAW,KAAK,OAEjC,GAAI,EAAAG,IAAS,MAAQC,EAAS,OAASnB,EAAe,WAElD,EAAAoB,EAAaD,CAAQ,IAAML,GAAOK,EAAS,SAAWJ,EAAW,KAAK,IAE1E,OAAOG,CACT,CAGA,SAASG,EAAgBzB,EAA6BC,EAAmB,CACvE,GAAIA,EAAK,OAASG,EAAe,WAAY,CAC3C,MAAMsB,EAAQT,EAAUjB,EAAQC,EAAM,OAAO,EAE7C,GAAIyB,IAAU,OAAW,OAAOlB,EAAeR,EAAQ0B,CAAK,CAC9D,CAEA,MAAMxB,EAAQC,EAAWH,EAAQC,CAAI,EAErC,OAAOC,EAAM,OAASE,EAAe,eACjCW,EAAef,EAAQE,EAAM,MAAM,EACnCU,EAAcZ,EAAQE,CAAK,CACjC,CAGA,SAASyB,EAAS3B,EAA6BC,EAAqB2B,EAAY,CAG9E,OAFI3B,EAAK,OAASG,EAAe,kBAAoBH,EAAK,UAEtDA,EAAK,SAAS,OAASG,EAAe,YAAcH,EAAK,SAAS,OAAS,OAAe,GAEvFD,EAAO,QAAQC,EAAK,MAAM,IAAM2B,CACzC,CAEA,SAASC,EAAc7B,EAA6B8B,EAAqBC,EAAqBH,EAAY,CACxG,OAAOG,EAAK,OAAS3B,EAAe,SAAW2B,EAAK,QAAUjC,GAAW6B,EAAS3B,EAAQ8B,EAAMF,CAAI,CACtG,CAGA,SAASI,EAAQhC,EAA6BiC,EAAqBL,EAAY,CAC7E,OAAIK,EAAK,OAAS7B,EAAe,mBAAqB6B,EAAK,WAAa,KAC/DD,EAAQhC,EAAQiC,EAAK,KAAML,CAAI,GAAKI,EAAQhC,EAAQiC,EAAK,MAAOL,CAAI,EAGzEK,EAAK,OAAS7B,EAAe,kBAAoB6B,EAAK,WAAa,MAAc,GAE9EJ,EAAc7B,EAAQiC,EAAK,KAAMA,EAAK,MAAOL,CAAI,GAAKC,EAAc7B,EAAQiC,EAAK,MAAOA,EAAK,KAAML,CAAI,CAChH,CAGA,SAASM,EAAQjC,EAAmB,CAClC,GAAIA,EAAK,OAASG,EAAe,YAAa,MAAO,CAAE,KAAMH,EAAK,WAAY,KAAMA,EAAK,IAAI,EAE7F,GAAIA,EAAK,OAASG,EAAe,sBAAuB,MAAO,CAAE,KAAMH,EAAK,WAAY,KAAMA,EAAK,IAAI,EAEvG,GAAIA,EAAK,OAASG,EAAe,mBAAqBH,EAAK,WAAa,KACtE,MAAO,CAAE,KAAMA,EAAK,MAAO,KAAMA,EAAK,IAAI,CAI9C,CAOA,SAASkC,EAAkBnC,EAA6BC,EAA+B,CACrF,GAAI,CAACwB,EAAgBzB,EAAQC,EAAK,MAAM,EAAG,MAAO,GAElD,MAAM2B,EAAO5B,EAAO,QAAQC,EAAK,MAAM,EAEvC,OAAOD,EAAO,aAAaC,CAAI,EAAE,KAAKmC,GAAW,CAC/C,MAAMC,EAAQH,EAAQE,CAAQ,EAE9B,OAAOC,IAAU,QAAaC,EAAOrC,EAAMoC,EAAM,IAAI,GAAKL,EAAQhC,EAAQqC,EAAM,KAAMT,CAAI,CAC5F,CAAC,CACH,CAMA,SAASW,EAAiBvC,EAA6BC,EAAmB,CACxE,GAAIA,EAAK,OAASG,EAAe,WAAY,CAC3C,MAAMsB,EAAQT,EAAUjB,EAAQC,EAAM,SAAS,EAE/C,GAAIyB,IAAU,OAAW,OAAOhB,EAAcV,EAAQ0B,CAAK,CAC7D,CAEA,MAAMxB,EAAQC,EAAWH,EAAQC,CAAI,EAIrC,OAFIC,EAAM,OAASE,EAAe,kBAAoBF,EAAM,UAExDA,EAAM,SAAS,OAASE,EAAe,YAAcF,EAAM,SAAS,OAAS,UAAkB,GAE5FQ,EAAcV,EAAQc,EAAOZ,EAAM,MAAM,CAAC,CACnD"}
1
+ {"version":3,"file":"retired-vocabulary.js","sources":["../src/retired-vocabulary.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils';\n\nimport { propertyName } from './ast';\nimport { isContextFunction, methodPath } from './declaration-ingress';\nimport { definitionOf, isApi, localValue, unwrap } from './model-bindings';\n\n/** D349: the two factories of the Resource family. Both answer the object whose verbs a migration renames. */\nconst RESOURCE_FACTORIES = new Set(['resource.live', 'resource.load']);\n/**\n * A value this module proved is a Resource of this library: the `resource.load` or `resource.live` that declared it,\n * read past the name it was given. The shape decides first and the resolution second, so a call that cannot be a\n * declaration never pays for the context it would have been declared on (D323).\n */\nfunction isResourceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.CallExpression) return false;\n\n const named = methodPath(source, value.callee);\n\n return named !== undefined && RESOURCE_FACTORIES.has(named.method) && isContextFunction(source, named.fn);\n}\n\n/**\n * `useResource(book)`: the record of hooks, under whatever local name this file gave the import. The module is\n * `@opetope/react` exactly — a host's own `./hooks` must not pass for ours, which is what `exact` asks for (D309).\n */\nfunction isResourceHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useResource', true)\n );\n}\n\n/**\n * D411: `useCommand(save)` — the one consumer of a command, under whatever local name this file gave the import.\n * The module is `@opetope/react` exactly, for the reason `useResource` is asked exactly (D309).\n */\nfunction isCommandHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useCommand', true)\n );\n}\n\n/**\n * D411, D415: the surface `defineSurface` answers, under whatever local name this file gave the import. The module\n * is `@opetope/navigation/react` exactly, for the reason `useResource` is asked exactly (D309): `policy` is a word\n * a host writes about its own objects all day, so the rule asks the symbol and never the spelling.\n */\nfunction isSurfaceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression &&\n isApi(source, value.callee, '@opetope/navigation/react', 'defineSurface', true)\n );\n}\n\n/** `.state` read on a Resource of this library or on the record `useResource` answers. */\nfunction isStateMember(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'state') return false;\n\n const object = unwrap(node.object);\n\n return isResourceValue(source, object) || isResourceHook(source, object);\n}\n\n/** `book.state.getSnapshot()`: the state as a model reads it. */\nfunction isSnapshotRead(source: TSESLint.SourceCode, callee: TSESTree.Node): boolean {\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return false;\n\n return isStateMember(source, unwrap(callee.object));\n}\n\n/**\n * `const { state } = useResource(book)`: the value a name was destructured out of, under the key it was taken by.\n * `localValue` answers the whole initializer for such a name, which is the record and not the member, so the key is\n * read here instead — the way a context member is read off the pattern that bound it.\n */\nfunction takenFrom(source: TSESLint.SourceCode, node: TSESTree.Identifier, key: string): TSESTree.Node | undefined {\n const definition = definitionOf(source, node);\n\n if (definition?.type !== TSESLint.Scope.DefinitionType.Variable) return undefined;\n\n const { init } = definition.node;\n const property = definition.name.parent;\n\n if (init === null || property.type !== AST_NODE_TYPES.Property) return undefined;\n\n if (propertyName(property) !== key || property.parent !== definition.node.id) return undefined;\n\n return init;\n}\n\n/** The `ResourceState` a name holds: the member itself, one value read out of it, or the key it was taken from. */\nfunction isResourceState(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'state');\n\n if (taken !== undefined) return isResourceHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n return value.type === AST_NODE_TYPES.CallExpression\n ? isSnapshotRead(source, value.callee)\n : isStateMember(source, value);\n}\n\n/**\n * D462: `activity` is gone from every member of `ResourceState`, so a read of it is retired wherever the expression\n * is one of ours — there is no branch left in which the field survives. D375 had retired it on `loading` alone, so\n * the rule had to prove which branch a read stood in; that machinery is gone with the half-retirement it served.\n */\nfunction isStateActivity(source: TSESLint.SourceCode, node: TSESTree.MemberExpression): boolean {\n return isResourceState(source, node.object);\n}\n\n/**\n * D411: the settled answer a consumer keeps — `.outcome` read on a `useCommand`, or the name it was destructured\n * into. `status` is a common word, so the rule asks the symbol: nothing but this object is reported.\n */\nfunction isCommandOutcome(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'outcome');\n\n if (taken !== undefined) return isCommandHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.MemberExpression || value.computed) return false;\n\n if (value.property.type !== AST_NODE_TYPES.Identifier || value.property.name !== 'outcome') return false;\n\n return isCommandHook(source, unwrap(value.object));\n}\n\nexport { isCommandOutcome, isResourceHook, isResourceValue, isStateActivity, isSurfaceValue };\n"],"names":["RESOURCE_FACTORIES","isResourceValue","source","node","value","localValue","AST_NODE_TYPES","named","methodPath","isContextFunction","isResourceHook","isApi","isCommandHook","isSurfaceValue","isStateMember","object","unwrap","isSnapshotRead","callee","takenFrom","key","definition","definitionOf","TSESLint","init","property","propertyName","isResourceState","taken","isStateActivity","isCommandOutcome"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as a}from"@typescript-eslint/utils";import{propertyName as m,isFunctionLike as h,calleeName as y,patternKeys as d,memberRoot as l,memberFirstStep as S}from"../ast.js";import{createRule as w}from"../rule.js";const I=new Set(["pipe","register","slot"]),x=new Set(["exports","imports","own","read"]),b=new Set(["exports","imports","own"]);function E(n){const{parent:e}=n;if(e.type!==a.CallExpression||e.arguments.length<2)return!1;const t=y(e);return t!==void 0&&I.has(t)&&e.arguments.at(-1)===n}function c(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==a.ObjectPattern))return[...d(e)].some(t=>x.has(t))?e:void 0}function T(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==a.Identifier))return e}function g(n,e){const t=l(e),r=S(e);return t.type!==a.Identifier||t.name!==n.name?!1:r!==void 0&&b.has(r)}function A(n,e){const t=l(e);return t.type!==a.Identifier?!1:b.has(t.name)&&d(n).has(t.name)}function R(n,e){const t=c(n);if(t!==void 0)return A(t,e)?"read":void 0;const r=T(n);if(r!==void 0)return g(r,e)?`${r.name}.read`:void 0}function O(n,e){const t=e.properties.find(s=>{const i=m(s);return i===void 0||i>"read"});if(t!==void 0)return n.insertTextBefore(t,"read, ");const r=e.properties.at(-1);return r===void 0?void 0:n.insertTextAfter(r,", read")}function C(n,e){return n.params.some(t=>t.type===a.Identifier&&t.name===e.name)}function N(n,e){const{parent:t}=n;return t.type===a.ObjectExpression&&E(t)||c(e)!==void 0}const B=w({create(n){const e=(r,s)=>{const i=R(r,s);i!==void 0&&n.report({fix(p){const o=c(r),u=p.replaceText(s,`${i}(${n.sourceCode.getText(s)})`);if(o===void 0||d(o).has("read"))return[u];const f=O(p,o);return f===void 0?[u]:[u,f]},messageId:"enabledReturnsSource",node:s})},t=r=>{if(r.async){n.report({messageId:"enabledIsAsync",node:r});return}if(r.type!==a.ArrowFunctionExpression)return;const{body:s}=r;if(s.type===a.MemberExpression){e(r,s);return}s.type===a.Identifier&&!C(r,s)&&n.report({messageId:"enabledReturnsBareSource",node:s})};return{Property(r){m(r)!=="enabled"||!h(r.value)||N(r,r.value)&&t(r.value)}}},meta:{docs:{description:"A contribution `enabled` predicate answers the visibility fact, not the source that carries it."},fixable:"code",messages:{enabledIsAsync:"A contribution `enabled` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).",enabledReturnsBareSource:"A contribution `enabled` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).",enabledReturnsSource:"A contribution `enabled` predicate returns the source instead of the fact; read it with `read(...)` (D220)."},schema:[],type:"problem"},name:"enabled-predicate"});export{B as enabledPredicate};
1
+ import{AST_NODE_TYPES as i}from"@typescript-eslint/utils";import{bodyRecordEntries as E}from"../feature-body-records.js";import{isFunction as b,localValue as h}from"../model-bindings.js";import{propertyName as y,findProperty as I,calleeName as C,patternKeys as f,memberRoot as S,memberFirstStep as g}from"../ast.js";import{createRule as x}from"../rule.js";const R=new Set(["pipe","register","slot"]),T=new Set(["exports","imports","own","read"]),w=new Set(["exports","imports","own"]);function A(e){const{parent:n}=e;if(n.type!==i.CallExpression||n.arguments.length<2)return!1;const t=C(n);return t!==void 0&&R.has(t)&&n.arguments.at(-1)===e}function m(e){const[n]=e.params;if(!(e.params.length!==1||n?.type!==i.ObjectPattern))return[...f(n)].some(t=>T.has(t))?n:void 0}function O(e){const[n]=e.params;if(!(e.params.length!==1||n?.type!==i.Identifier))return n}function N(e,n){const t=S(n),o=g(n);return t.type!==i.Identifier||t.name!==e.name?!1:o!==void 0&&w.has(o)}function P(e,n){const t=S(n);return t.type!==i.Identifier?!1:w.has(t.name)&&f(e).has(t.name)}function B(e,n){const t=m(e);if(t!==void 0)return P(t,n)?"read":void 0;const o=O(e);if(o!==void 0)return N(o,n)?`${o.name}.read`:void 0}function D(e,n){const t=n.properties.find(u=>{const r=y(u);return r===void 0||r>"read"});if(t!==void 0)return e.insertTextBefore(t,"read, ");const o=n.properties.at(-1);return o===void 0?void 0:e.insertTextAfter(o,", read")}function v(e,n){return e.params.some(t=>t.type===i.Identifier&&t.name===n.name)}function _(e,n){const{parent:t}=e;return t.type===i.ObjectExpression&&A(t)||m(n)!==void 0}const F=x({create(e){const n=[],t=new Set,o=(r,s)=>{const d=B(r,s);d!==void 0&&e.report({fix(c){const a=m(r),p=c.replaceText(s,`${d}(${e.sourceCode.getText(s)})`);if(a===void 0||f(a).has("read"))return[p];const l=D(c,a);return l===void 0?[p]:[p,l]},messageId:"enabledReturnsSource",node:s})},u=r=>{if(t.has(r))return;if(t.add(r),r.async){e.report({messageId:"enabledIsAsync",node:r});return}if(r.type!==i.ArrowFunctionExpression)return;const{body:s}=r;if(s.type===i.MemberExpression){o(r,s);return}s.type===i.Identifier&&!v(r,s)&&e.report({messageId:"enabledReturnsBareSource",node:s})};return{CallExpression(r){n.push(r)},"Program:exit"(){for(const r of n)for(const s of E(e.sourceCode,r)){if(s.options===void 0)continue;const d=h(e.sourceCode,s.options);if(d.type!==i.ObjectExpression)continue;const c=I(d,"enabled");if(c===void 0)continue;const a=h(e.sourceCode,c.value);b(a)&&u(a)}},Property(r){y(r)!=="enabled"||!b(r.value)||_(r,r.value)&&u(r.value)}}},meta:{docs:{description:"A contribution `enabled` predicate answers the visibility fact, not the source that carries it."},fixable:"code",messages:{enabledIsAsync:"A contribution `enabled` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).",enabledReturnsBareSource:"A contribution `enabled` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).",enabledReturnsSource:"A contribution `enabled` predicate returns the source instead of the fact; read it with `read(...)` (D220)."},schema:[],type:"problem"},name:"enabled-predicate"});export{F as enabledPredicate};
2
2
  //# sourceMappingURL=enabled-predicate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"enabled-predicate.js","sources":["../../src/rules/enabled-predicate.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport type { FunctionLike } from '../ast';\nimport { calleeName, isFunctionLike, memberFirstStep, memberRoot, patternKeys, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'enabledIsAsync' | 'enabledReturnsBareSource' | 'enabledReturnsSource';\n\nconst CONTRIBUTION_BUILDERS = new Set(['pipe', 'register', 'slot']);\nconst CONTEXT_KEYS = new Set(['exports', 'imports', 'own', 'read']);\nconst CONTEXT_SOURCES = new Set(['exports', 'imports', 'own']);\n\n/** The options object of `slot(target, contribution, {…})`, `pipe(target, {fold}, {…})`, `register(…)`. */\nfunction isBuilderOptions(node: TSESTree.ObjectExpression): boolean {\n const { parent } = node;\n\n if (parent.type !== AST_NODE_TYPES.CallExpression || parent.arguments.length < 2) return false;\n\n const name = calleeName(parent);\n\n return name !== undefined && CONTRIBUTION_BUILDERS.has(name) && parent.arguments.at(-1) === node;\n}\n\n/** The destructured evaluation context of a contribution predicate: `({ exports, imports, own, read })`. */\nfunction contextPattern(fn: FunctionLike): TSESTree.ObjectPattern | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.ObjectPattern) return undefined;\n\n return [...patternKeys(parameter)].some(key => CONTEXT_KEYS.has(key)) ? parameter : undefined;\n}\n\n/** The same context taken as one named parameter: `context => context.imports.platform.submitAllowed`. */\nfunction contextIdentifier(fn: FunctionLike): TSESTree.Identifier | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return parameter;\n}\n\nfunction isNamedContextAccess(parameter: TSESTree.Identifier, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n const step = memberFirstStep(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier || root.name !== parameter.name) return false;\n\n return step !== undefined && CONTEXT_SOURCES.has(step);\n}\n\nfunction isDestructuredContextAccess(pattern: TSESTree.ObjectPattern, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier) return false;\n\n return CONTEXT_SOURCES.has(root.name) && patternKeys(pattern).has(root.name);\n}\n\n/** The reader a fix would call, or `undefined` when the body reaches something other than the instance context. */\nfunction readerFor(fn: FunctionLike, body: TSESTree.MemberExpression): string | undefined {\n const pattern = contextPattern(fn);\n\n if (pattern !== undefined) return isDestructuredContextAccess(pattern, body) ? 'read' : undefined;\n\n const parameter = contextIdentifier(fn);\n\n if (parameter === undefined) return undefined;\n\n return isNamedContextAccess(parameter, body) ? `${parameter.name}.read` : undefined;\n}\n\n/** `read` joins the destructured context in place, so the fixed predicate keeps the order authors write. */\nfunction insertReadKey(fixer: TSESLint.RuleFixer, pattern: TSESTree.ObjectPattern): TSESLint.RuleFix | undefined {\n const successor = pattern.properties.find(property => {\n const name = propertyName(property);\n\n return name === undefined || name > 'read';\n });\n\n if (successor !== undefined) return fixer.insertTextBefore(successor, 'read, ');\n\n const last = pattern.properties.at(-1);\n\n return last === undefined ? undefined : fixer.insertTextAfter(last, ', read');\n}\n\nfunction isOwnParameter(fn: FunctionLike, identifier: TSESTree.Identifier): boolean {\n return fn.params.some(\n parameter => parameter.type === AST_NODE_TYPES.Identifier && parameter.name === identifier.name,\n );\n}\n\nfunction isContributionEnabled(property: TSESTree.Property, fn: FunctionLike): boolean {\n const { parent } = property;\n\n return (\n (parent.type === AST_NODE_TYPES.ObjectExpression && isBuilderOptions(parent)) || contextPattern(fn) !== undefined\n );\n}\n\nconst enabledPredicate = createRule<[], MessageIds>({\n create(context) {\n const reportSource = (fn: FunctionLike, body: TSESTree.MemberExpression): void => {\n const reader = readerFor(fn, body);\n\n if (reader === undefined) return;\n\n context.report({\n fix(fixer) {\n const pattern = contextPattern(fn);\n const wrapped = fixer.replaceText(body, `${reader}(${context.sourceCode.getText(body)})`);\n\n if (pattern === undefined || patternKeys(pattern).has('read')) return [wrapped];\n\n const inserted = insertReadKey(fixer, pattern);\n\n return inserted === undefined ? [wrapped] : [wrapped, inserted];\n },\n messageId: 'enabledReturnsSource',\n node: body,\n });\n };\n\n const check = (fn: FunctionLike): void => {\n if (fn.async) {\n context.report({ messageId: 'enabledIsAsync', node: fn });\n\n return;\n }\n\n if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression) return;\n\n const { body } = fn;\n\n if (body.type === AST_NODE_TYPES.MemberExpression) {\n reportSource(fn, body);\n\n return;\n }\n\n if (body.type === AST_NODE_TYPES.Identifier && !isOwnParameter(fn, body)) {\n context.report({ messageId: 'enabledReturnsBareSource', node: body });\n }\n };\n\n return {\n Property(node): void {\n if (propertyName(node) !== 'enabled' || !isFunctionLike(node.value)) return;\n\n if (isContributionEnabled(node, node.value)) check(node.value);\n },\n };\n },\n meta: {\n docs: {\n description: 'A contribution `enabled` predicate answers the visibility fact, not the source that carries it.',\n },\n fixable: 'code',\n messages: {\n enabledIsAsync:\n 'A contribution `enabled` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).',\n enabledReturnsBareSource:\n 'A contribution `enabled` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).',\n enabledReturnsSource:\n 'A contribution `enabled` predicate returns the source instead of the fact; read it with `read(...)` (D220).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'enabled-predicate',\n});\n\nexport { enabledPredicate };\n"],"names":["CONTRIBUTION_BUILDERS","CONTEXT_KEYS","CONTEXT_SOURCES","isBuilderOptions","node","parent","AST_NODE_TYPES","name","calleeName","contextPattern","fn","parameter","patternKeys","key","contextIdentifier","isNamedContextAccess","body","root","memberRoot","step","memberFirstStep","isDestructuredContextAccess","pattern","readerFor","insertReadKey","fixer","successor","property","propertyName","last","isOwnParameter","identifier","isContributionEnabled","enabledPredicate","createRule","context","reportSource","reader","wrapped","inserted","check","isFunctionLike"],"mappings":"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"}
1
+ {"version":3,"file":"enabled-predicate.js","sources":["../../src/rules/enabled-predicate.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { bodyRecordEntries } from '../feature-body-records';\nimport type { FunctionNode } from '../model-bindings';\nimport { isFunction, localValue } from '../model-bindings';\nimport { calleeName, findProperty, memberFirstStep, memberRoot, patternKeys, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'enabledIsAsync' | 'enabledReturnsBareSource' | 'enabledReturnsSource';\n\nconst CONTRIBUTION_BUILDERS = new Set(['pipe', 'register', 'slot']);\nconst CONTEXT_KEYS = new Set(['exports', 'imports', 'own', 'read']);\nconst CONTEXT_SOURCES = new Set(['exports', 'imports', 'own']);\n\n/** The options object of `slot(target, contribution, {…})`, `pipe(target, {fold}, {…})`, `register(…)`. */\nfunction isBuilderOptions(node: TSESTree.ObjectExpression): boolean {\n const { parent } = node;\n\n if (parent.type !== AST_NODE_TYPES.CallExpression || parent.arguments.length < 2) return false;\n\n const name = calleeName(parent);\n\n return name !== undefined && CONTRIBUTION_BUILDERS.has(name) && parent.arguments.at(-1) === node;\n}\n\n/** The destructured evaluation context of a contribution predicate: `({ exports, imports, own, read })`. */\nfunction contextPattern(fn: FunctionNode): TSESTree.ObjectPattern | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.ObjectPattern) return undefined;\n\n return [...patternKeys(parameter)].some(key => CONTEXT_KEYS.has(key)) ? parameter : undefined;\n}\n\n/** The same context taken as one named parameter: `context => context.imports.platform.submitAllowed`. */\nfunction contextIdentifier(fn: FunctionNode): TSESTree.Identifier | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return parameter;\n}\n\nfunction isNamedContextAccess(parameter: TSESTree.Identifier, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n const step = memberFirstStep(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier || root.name !== parameter.name) return false;\n\n return step !== undefined && CONTEXT_SOURCES.has(step);\n}\n\nfunction isDestructuredContextAccess(pattern: TSESTree.ObjectPattern, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier) return false;\n\n return CONTEXT_SOURCES.has(root.name) && patternKeys(pattern).has(root.name);\n}\n\n/** The reader a fix would call, or `undefined` when the body reaches something other than the instance context. */\nfunction readerFor(fn: FunctionNode, body: TSESTree.MemberExpression): string | undefined {\n const pattern = contextPattern(fn);\n\n if (pattern !== undefined) return isDestructuredContextAccess(pattern, body) ? 'read' : undefined;\n\n const parameter = contextIdentifier(fn);\n\n if (parameter === undefined) return undefined;\n\n return isNamedContextAccess(parameter, body) ? `${parameter.name}.read` : undefined;\n}\n\n/** `read` joins the destructured context in place, so the fixed predicate keeps the order authors write. */\nfunction insertReadKey(fixer: TSESLint.RuleFixer, pattern: TSESTree.ObjectPattern): TSESLint.RuleFix | undefined {\n const successor = pattern.properties.find(property => {\n const name = propertyName(property);\n\n return name === undefined || name > 'read';\n });\n\n if (successor !== undefined) return fixer.insertTextBefore(successor, 'read, ');\n\n const last = pattern.properties.at(-1);\n\n return last === undefined ? undefined : fixer.insertTextAfter(last, ', read');\n}\n\nfunction isOwnParameter(fn: FunctionNode, identifier: TSESTree.Identifier): boolean {\n return fn.params.some(\n parameter => parameter.type === AST_NODE_TYPES.Identifier && parameter.name === identifier.name,\n );\n}\n\nfunction isContributionEnabled(property: TSESTree.Property, fn: FunctionNode): boolean {\n const { parent } = property;\n\n return (\n (parent.type === AST_NODE_TYPES.ObjectExpression && isBuilderOptions(parent)) || contextPattern(fn) !== undefined\n );\n}\n\nconst enabledPredicate = createRule<[], MessageIds>({\n create(context) {\n const calls: TSESTree.CallExpression[] = [];\n const checked = new Set<FunctionNode>();\n const reportSource = (fn: FunctionNode, body: TSESTree.MemberExpression): void => {\n const reader = readerFor(fn, body);\n\n if (reader === undefined) return;\n\n context.report({\n fix(fixer) {\n const pattern = contextPattern(fn);\n const wrapped = fixer.replaceText(body, `${reader}(${context.sourceCode.getText(body)})`);\n\n if (pattern === undefined || patternKeys(pattern).has('read')) return [wrapped];\n\n const inserted = insertReadKey(fixer, pattern);\n\n return inserted === undefined ? [wrapped] : [wrapped, inserted];\n },\n messageId: 'enabledReturnsSource',\n node: body,\n });\n };\n\n const check = (fn: FunctionNode): void => {\n if (checked.has(fn)) return;\n checked.add(fn);\n if (fn.async) {\n context.report({ messageId: 'enabledIsAsync', node: fn });\n\n return;\n }\n\n if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression) return;\n\n const { body } = fn;\n\n if (body.type === AST_NODE_TYPES.MemberExpression) {\n reportSource(fn, body);\n\n return;\n }\n\n if (body.type === AST_NODE_TYPES.Identifier && !isOwnParameter(fn, body)) {\n context.report({ messageId: 'enabledReturnsBareSource', node: body });\n }\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n 'Program:exit'(): void {\n for (const call of calls) {\n for (const entry of bodyRecordEntries(context.sourceCode, call)) {\n if (entry.options === undefined) continue;\n const options = localValue(context.sourceCode, entry.options);\n if (options.type !== AST_NODE_TYPES.ObjectExpression) continue;\n const enabled = findProperty(options, 'enabled');\n if (enabled === undefined) continue;\n const predicate = localValue(context.sourceCode, enabled.value);\n if (isFunction(predicate)) check(predicate);\n }\n }\n },\n Property(node): void {\n if (propertyName(node) !== 'enabled' || !isFunction(node.value)) return;\n\n if (isContributionEnabled(node, node.value)) check(node.value);\n },\n };\n },\n meta: {\n docs: {\n description: 'A contribution `enabled` predicate answers the visibility fact, not the source that carries it.',\n },\n fixable: 'code',\n messages: {\n enabledIsAsync:\n 'A contribution `enabled` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).',\n enabledReturnsBareSource:\n 'A contribution `enabled` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).',\n enabledReturnsSource:\n 'A contribution `enabled` predicate returns the source instead of the fact; read it with `read(...)` (D220).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'enabled-predicate',\n});\n\nexport { enabledPredicate };\n"],"names":["CONTRIBUTION_BUILDERS","CONTEXT_KEYS","CONTEXT_SOURCES","isBuilderOptions","node","parent","AST_NODE_TYPES","name","calleeName","contextPattern","fn","parameter","patternKeys","key","contextIdentifier","isNamedContextAccess","body","root","memberRoot","step","memberFirstStep","isDestructuredContextAccess","pattern","readerFor","insertReadKey","fixer","successor","property","propertyName","last","isOwnParameter","identifier","isContributionEnabled","enabledPredicate","createRule","context","calls","checked","reportSource","reader","wrapped","inserted","check","call","entry","bodyRecordEntries","options","localValue","enabled","findProperty","predicate","isFunction"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- type MessageIds = 'commandLane' | 'commandLatest' | 'commandName' | 'commandOnce' | 'commandOutcomeStatus' | 'commandPolicy' | 'commandQueueBy' | 'conditionFrom' | 'contextCall' | 'contextCalls' | 'contributionWhen' | 'deliveryKey' | 'deliveryKind' | 'deliveryReject' | 'devtoolsResourceActivity' | 'effectOnDispose' | 'effectSkipInitial' | 'effectWhen' | 'eventBackpressure' | 'fromExternal' | 'loadingActivity' | 'navigatorPolicy' | 'resourceDisposer' | 'resourceInvalidate' | 'runtimeInternalEntry' | 'scopeKeyed' | 'useCommands';
1
+ type MessageIds = 'commandLane' | 'commandLatest' | 'commandName' | 'commandOnce' | 'commandOutcomeStatus' | 'commandPolicy' | 'commandQueueBy' | 'conditionFrom' | 'contextCall' | 'contextCalls' | 'contributionWhen' | 'deliveryKey' | 'deliveryKind' | 'deliveryReject' | 'effectOnDispose' | 'effectSkipInitial' | 'effectWhen' | 'eventBackpressure' | 'fromExternal' | 'navigatorPolicy' | 'resourceActivity' | 'resourceDisposer' | 'resourceInvalidate' | 'runtimeInternalEntry' | 'scopeKeyed' | 'stateActivity' | 'useCommands';
2
2
  declare const noRetiredVocabulary: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
3
3
  name: string;
4
4
  };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as s}from"@typescript-eslint/utils";import{propertyName as u,findProperty as O}from"../ast.js";import{methodPath as v,isContextFunction as M}from"../declaration-ingress.js";import{isFeatureSection as A,isApi as N,localValue as m}from"../model-bindings.js";import{isResourceValue as T,isResourceHook as x,isLoadingActivity as S,isSurfaceValue as P,isCommandOutcome as V}from"../retired-vocabulary.js";import{createRule as _}from"../rule.js";const d=new Map([["lane","commandLane"],["once","commandOnce"],["policy","commandPolicy"],["queueBy","commandQueueBy"]]),c=new Map([["concurrency",new Map([["latest","commandLatest"]])]]),W=new Map([["onDispose","effectOnDispose"],["skipInitial","effectSkipInitial"],["when","effectWhen"]]),B=new Map([["backpressure","eventBackpressure"]]),L=new Map([["key","deliveryKey"],["kind","deliveryKind"]]),j=new Map,g=["backpressure","delivery"],F=new Map([["overflow",new Map([["reject","deliveryReject"]])]]),p=new Map([["when","contributionWhen"]]),q=new Map([["pipe",{at:[2],carries:[],words:p}],["register",{at:[2],carries:[],words:p}],["slot",{at:[2],carries:[],words:p}]]),K=new Map([["from","conditionFrom"]]),U=new Map([["call",{at:[1,2],carries:[],values:c,words:d}],["calls",{at:[2],carries:[],values:c,words:d}],["command",{at:[1,2],carries:[],values:c,words:d}],["effect",{at:[2],carries:[],words:W}],["event",{at:[3],carries:g,words:B}],["resource.live",{at:[0],carriedValues:F,carries:g,words:j}],["select",{at:[2],carries:[],values:c,words:d}]]),Y=new Map([["activity","loadingActivity"],["invalidate","resourceInvalidate"],["policy","navigatorPolicy"],["status","commandOutcomeStatus"]]),z=new Map([["call","contextCall"],["calls","contextCalls"],["scope.keyed","scopeKeyed"]]),H=new Map([["ApplicationConditionBinding",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["ApplicationExecution",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["ApplicationImportBinding",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["Call",{messageId:"commandName",replacement:"Command"}],["CallError",{messageId:"commandName",replacement:"CommandError"}],["CallInputArgs",{messageId:"commandName",replacement:"CommandInputArgs"}],["CallTimeoutError",{messageId:"commandName",replacement:"CommandTimeoutError"}],["NavigatorPolicy",{messageId:"navigatorPolicy",replacement:"NavigatorCatalogue"}],["ResourceActivity",{messageId:"devtoolsResourceActivity",module:"@opetope/devtools",replacement:"ResourceNodeActivity"}],["ResourceDisposer",{messageId:"resourceDisposer",replacement:"Disposer"}],["RunCallOptions",{messageId:"commandName",replacement:"RunCommandOptions"}],["externalReadable",{messageId:"fromExternal",replacement:"fromExternal"}],["runCall",{messageId:"commandName",replacement:"runCommand"}],["useCommands",{messageId:"useCommands",replacement:"useModel"}]]);function Q(a){return a.type===s.MemberExpression?a.property:a}function G(a){return a.imported.type===s.Identifier?a.imported.name:a.imported.value}const J=_({create(a){const r=a.sourceCode,b=e=>{const t=v(r,e.callee);if(t===void 0)return;const n={modifiers:U.get(t.method),word:z.get(t.method)};if(!(n.modifiers===void 0&&n.word===void 0))return M(r,t.fn)?n:void 0},h=(e,t)=>{const n=e.arguments[t];if(n===void 0||n.type===s.SpreadElement)return;const o=m(r,n);return o.type===s.ObjectExpression?o:void 0},D=e=>{const t=v(r,e.callee),n=t===void 0?void 0:q.get(t.method);if(!(t===void 0||n===void 0))return A(r,t.fn,"provides",!0)?n:void 0},I=e=>{if(N(r,e.callee,"@opetope/runtime","defineCondition",!0))return h(e,1)},l=(e,t)=>{for(const n of e.properties){if(n.type!==s.Property)continue;const o=u(n),i=o===void 0?void 0:t.get(o);i!==void 0&&a.report({messageId:i,node:n.key})}},C=(e,t)=>{const n=u(e),o=n===void 0?void 0:t.get(n);if(o===void 0)return;const i=m(r,e.value);return i.type===s.Literal&&typeof i.value=="string"?o.get(i.value):void 0},f=(e,t)=>{for(const n of e.properties){if(n.type!==s.Property)continue;const o=C(n,t);o!==void 0&&a.report({messageId:o,node:n.value})}},R=(e,t,n)=>{const o=O(e,t),i=o===void 0?void 0:m(r,o.value);i?.type===s.ObjectExpression&&(l(i,L),n.carriedValues!==void 0&&f(i,n.carriedValues))},w=(e,t)=>{for(const n of t.at){const o=h(e,n);if(o!==void 0){l(o,t.words),t.values!==void 0&&f(o,t.values);for(const i of t.carries)R(o,i,t)}}},y=e=>T(r,e)||x(r,e),k=(e,t)=>t==="resourceInvalidate"?y(e.object):t==="loadingActivity"?S(r,e):t==="navigatorPolicy"?P(r,e.object):V(r,e.object),E=(e,t)=>{const n=Y.get(t);return n!==void 0&&k(e,n)?n:void 0};return{CallExpression(e){const t=D(e);t!==void 0&&w(e,t);const n=I(e);n!==void 0&&l(n,K);const o=b(e);o!==void 0&&(o.word!==void 0&&a.report({messageId:o.word,node:Q(e.callee)}),o.modifiers!==void 0&&w(e,o.modifiers))},ImportDeclaration(e){if(e.source.value.startsWith("@opetope/"))for(const t of e.specifiers){if(t.type!==s.ImportSpecifier)continue;const n=G(t),o=H.get(n);o!==void 0&&(o.module===void 0||o.module===e.source.value)&&a.report({data:{replacement:o.replacement,word:n},messageId:o.messageId,node:t.imported})}},MemberExpression(e){if(e.computed||e.property.type!==s.Identifier)return;const t=E(e,e.property.name);t!==void 0&&a.report({messageId:t,node:e.property})},Property(e){if(u(e)!=="invalidate"||e.parent.type!==s.ObjectPattern)return;const t=e.parent.parent;t.type!==s.VariableDeclarator||t.init===null||y(t.init)&&a.report({messageId:"resourceInvalidate",node:e.key})}}},meta:{docs:{description:"A word the D365\u2013D411 wave or a decision after it retired is named with its replacement, never rewritten."},messages:{commandLane:'`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: "latest" }` where `policy: "latest"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',commandLatest:'`concurrency: "latest"` is now `concurrency: { pending: "latest" }`, and `{ lane, pending: "latest" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',commandName:"`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).",commandOnce:"`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).",commandOutcomeStatus:'`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged \u2014 `{ kind: "ok", value }`, `{ kind: "failed", error }`, `{ kind: "cancelled", reason }` (D411).',commandPolicy:'`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: "latest" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value \u2014 `{ by }`, `{ by, lane }` or `{ lane, pending: "latest" }` \u2014 so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',commandQueueBy:"`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).",conditionFrom:"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads \u2014 `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).",contextCall:"`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` \u2014 and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => \u2026`), a constant names it (`const refill: Command<Refill, void> = ctx.command(\u2026)`), and the annotation of that context \u2014 `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature \u2014 is what is left; a feature has the last two only. `command<Deposit>(\u2026)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).",contextCalls:'`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, "name")` answers one command and `select(source, ["a", "b"])` a record of them (D388).',contributionWhen:"`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the other mechanism \u2014 the declared `Condition`s of `defineFeature({ when })`, which the application resolves before anything exists. `scope.while` keeps its own `when` (D378, D411).",deliveryKey:'Inside a delivery record `key` is now `by`: `{ pending: "latest", by }` keeps one waiting place per key instead of one for the whole node (D374).',deliveryKind:'Inside a delivery record `kind` is now `pending`: `kind: "latest"` is `pending: "latest"`, and `kind: "queue"` is `pending: "all"` \u2014 the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',deliveryReject:'`overflow: "reject"` is now `overflow: "drop"`: one word at both nodes for \xABthe new one does not enter and `emit` answers `false`\xBB. `"flush-oldest"` is unchanged and still the default of a keyed delivery (D369, D411).',devtoolsResourceActivity:"`ResourceActivity` of `@opetope/devtools` is now `ResourceNodeActivity`: `@opetope/core` exports a `ResourceActivity` of its own \u2014 the union a `ready` state carries \u2014 and one name carried two unrelated types on two public entries. Import the core one under its own name (D411).",effectOnDispose:"`onDispose` is now `finalize`: an effect has two releases \u2014 the disposer a run returns, and the one the node itself gets once \u2014 and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).",effectSkipInitial:"`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite \u2014 which is why this rule names it and leaves the rewrite to you (D379).",effectWhen:"`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => \u2026`; a predicate whose whole job is to refuse the first run \u2014 `(_current, previous) => previous !== undefined` \u2014 is `initial: false`. This rule does not guess between them. The word `when` is untouched at a feature and at `scope.while`; at a contribution it is now `enabled` (D379, D411).",eventBackpressure:'`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: "latest"` for one waiting place that the newest payload takes, or the record \u2014 `{ pending, by }`, `{ pending, capacity, overflow }` \u2014 for the rest (D374).',fromExternal:"`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).",loadingActivity:"A `loading` state carries `failure`, not `activity`: read `state.failure` \u2014 `undefined` is \xABboth channels are still viable\xBB, and otherwise `channel` names the one that died terminally and `error` carries its failure. A `ready` state still carries `activity` whole, so this is one branch losing the field and not the field being renamed (D375).",navigatorPolicy:"`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data \u2014 a word when it has no parameters, a record when it has them (D411).",resourceDisposer:"`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell \u2014 `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).",resourceInvalidate:"`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means \xABmark stale, keep the data\xBB, so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).",runtimeInternalEntry:"`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).",scopeKeyed:"`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required \u2014 so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).",useCommands:"`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388)."},schema:[],type:"problem"},name:"no-retired-vocabulary"});export{J as noRetiredVocabulary};
1
+ import{AST_NODE_TYPES as s}from"@typescript-eslint/utils";import{propertyName as u,findProperty as O}from"../ast.js";import{methodPath as v,isContextFunction as M}from"../declaration-ingress.js";import{isFeatureSection as N,isApi as T,localValue as m}from"../model-bindings.js";import{isResourceValue as A,isResourceHook as S,isStateActivity as x,isSurfaceValue as P,isCommandOutcome as V}from"../retired-vocabulary.js";import{createRule as _}from"../rule.js";const d=new Map([["lane","commandLane"],["once","commandOnce"],["policy","commandPolicy"],["queueBy","commandQueueBy"]]),c=new Map([["concurrency",new Map([["latest","commandLatest"]])]]),W=new Map([["onDispose","effectOnDispose"],["skipInitial","effectSkipInitial"],["when","effectWhen"]]),B=new Map([["backpressure","eventBackpressure"]]),L=new Map([["key","deliveryKey"],["kind","deliveryKind"]]),j=new Map,g=["backpressure","delivery"],F=new Map([["overflow",new Map([["reject","deliveryReject"]])]]),p=new Map([["when","contributionWhen"]]),q=new Map([["pipe",{at:[2],carries:[],words:p}],["register",{at:[2],carries:[],words:p}],["slot",{at:[2],carries:[],words:p}]]),K=new Map([["from","conditionFrom"]]),U=new Map([["call",{at:[1,2],carries:[],values:c,words:d}],["calls",{at:[2],carries:[],values:c,words:d}],["command",{at:[1,2],carries:[],values:c,words:d}],["effect",{at:[2],carries:[],words:W}],["event",{at:[3],carries:g,words:B}],["resource.live",{at:[0],carriedValues:F,carries:g,words:j}],["select",{at:[2],carries:[],values:c,words:d}]]),Y=new Map([["activity","stateActivity"],["invalidate","resourceInvalidate"],["policy","navigatorPolicy"],["status","commandOutcomeStatus"]]),z=new Map([["call","contextCall"],["calls","contextCalls"],["scope.keyed","scopeKeyed"]]),H=new Map([["ApplicationConditionBinding",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["ApplicationExecution",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["ApplicationImportBinding",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["Call",{messageId:"commandName",replacement:"Command"}],["CallError",{messageId:"commandName",replacement:"CommandError"}],["CallInputArgs",{messageId:"commandName",replacement:"CommandInputArgs"}],["CallTimeoutError",{messageId:"commandName",replacement:"CommandTimeoutError"}],["NavigatorPolicy",{messageId:"navigatorPolicy",replacement:"NavigatorCatalogue"}],["ResourceActivity",{messageId:"resourceActivity",replacement:"ResourceNodeActivity"}],["ResourceDisposer",{messageId:"resourceDisposer",replacement:"Disposer"}],["RunCallOptions",{messageId:"commandName",replacement:"RunCommandOptions"}],["externalReadable",{messageId:"fromExternal",replacement:"fromExternal"}],["runCall",{messageId:"commandName",replacement:"runCommand"}],["useCommands",{messageId:"useCommands",replacement:"useModel"}]]);function Q(a){return a.type===s.MemberExpression?a.property:a}function G(a){return a.imported.type===s.Identifier?a.imported.name:a.imported.value}const J=_({create(a){const r=a.sourceCode,b=e=>{const t=v(r,e.callee);if(t===void 0)return;const n={modifiers:U.get(t.method),word:z.get(t.method)};if(!(n.modifiers===void 0&&n.word===void 0))return M(r,t.fn)?n:void 0},h=(e,t)=>{const n=e.arguments[t];if(n===void 0||n.type===s.SpreadElement)return;const o=m(r,n);return o.type===s.ObjectExpression?o:void 0},D=e=>{const t=v(r,e.callee),n=t===void 0?void 0:q.get(t.method);if(!(t===void 0||n===void 0))return N(r,t.fn,"provides",!0)?n:void 0},I=e=>{if(T(r,e.callee,"@opetope/runtime","defineCondition",!0))return h(e,1)},l=(e,t)=>{for(const n of e.properties){if(n.type!==s.Property)continue;const o=u(n),i=o===void 0?void 0:t.get(o);i!==void 0&&a.report({messageId:i,node:n.key})}},C=(e,t)=>{const n=u(e),o=n===void 0?void 0:t.get(n);if(o===void 0)return;const i=m(r,e.value);return i.type===s.Literal&&typeof i.value=="string"?o.get(i.value):void 0},f=(e,t)=>{for(const n of e.properties){if(n.type!==s.Property)continue;const o=C(n,t);o!==void 0&&a.report({messageId:o,node:n.value})}},k=(e,t,n)=>{const o=O(e,t),i=o===void 0?void 0:m(r,o.value);i?.type===s.ObjectExpression&&(l(i,L),n.carriedValues!==void 0&&f(i,n.carriedValues))},w=(e,t)=>{for(const n of t.at){const o=h(e,n);if(o!==void 0){l(o,t.words),t.values!==void 0&&f(o,t.values);for(const i of t.carries)k(o,i,t)}}},y=e=>A(r,e)||S(r,e),R=(e,t)=>t==="resourceInvalidate"?y(e.object):t==="stateActivity"?x(r,e):t==="navigatorPolicy"?P(r,e.object):V(r,e.object),E=(e,t)=>{const n=Y.get(t);return n!==void 0&&R(e,n)?n:void 0};return{CallExpression(e){const t=D(e);t!==void 0&&w(e,t);const n=I(e);n!==void 0&&l(n,K);const o=b(e);o!==void 0&&(o.word!==void 0&&a.report({messageId:o.word,node:Q(e.callee)}),o.modifiers!==void 0&&w(e,o.modifiers))},ImportDeclaration(e){if(e.source.value.startsWith("@opetope/"))for(const t of e.specifiers){if(t.type!==s.ImportSpecifier)continue;const n=G(t),o=H.get(n);o!==void 0&&(o.module===void 0||o.module===e.source.value)&&a.report({data:{replacement:o.replacement,word:n},messageId:o.messageId,node:t.imported})}},MemberExpression(e){if(e.computed||e.property.type!==s.Identifier)return;const t=E(e,e.property.name);t!==void 0&&a.report({messageId:t,node:e.property})},Property(e){if(u(e)!=="invalidate"||e.parent.type!==s.ObjectPattern)return;const t=e.parent.parent;t.type!==s.VariableDeclarator||t.init===null||y(t.init)&&a.report({messageId:"resourceInvalidate",node:e.key})}}},meta:{docs:{description:"A word the D365\u2013D411 wave or a decision after it retired is named with its replacement, never rewritten."},messages:{commandLane:'`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: "latest" }` where `policy: "latest"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',commandLatest:'`concurrency: "latest"` is now `concurrency: { pending: "latest" }`, and `{ lane, pending: "latest" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',commandName:"`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).",commandOnce:"`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).",commandOutcomeStatus:'`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged \u2014 `{ kind: "ok", value }`, `{ kind: "failed", error }`, `{ kind: "cancelled", reason }` (D411).',commandPolicy:'`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: "latest" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value \u2014 `{ by }`, `{ by, lane }` or `{ lane, pending: "latest" }` \u2014 so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',commandQueueBy:"`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).",conditionFrom:"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads \u2014 `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).",contextCall:"`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` \u2014 and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => \u2026`), a constant names it (`const refill: Command<Refill, void> = ctx.command(\u2026)`), and the annotation of that context \u2014 `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature \u2014 is what is left; a feature has the last two only. `command<Deposit>(\u2026)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).",contextCalls:'`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, "name")` answers one command and `select(source, ["a", "b"])` a record of them (D388).',contributionWhen:"`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the other mechanism \u2014 the declared `Condition`s of `defineFeature({ when })`, which the application resolves before anything exists. `scope.while` keeps its own `when` (D378, D411).",deliveryKey:'Inside a delivery record `key` is now `by`: `{ pending: "latest", by }` keeps one waiting place per key instead of one for the whole node (D374).',deliveryKind:'Inside a delivery record `kind` is now `pending`: `kind: "latest"` is `pending: "latest"`, and `kind: "queue"` is `pending: "all"` \u2014 the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',deliveryReject:'`overflow: "reject"` is now `overflow: "drop"`: one word at both nodes for \xABthe new one does not enter and `emit` answers `false`\xBB. `"flush-oldest"` is unchanged and still the default of a keyed delivery (D369, D411).',effectOnDispose:"`onDispose` is now `finalize`: an effect has two releases \u2014 the disposer a run returns, and the one the node itself gets once \u2014 and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).",effectSkipInitial:"`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite \u2014 which is why this rule names it and leaves the rewrite to you (D379).",effectWhen:"`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => \u2026`; a predicate whose whole job is to refuse the first run \u2014 `(_current, previous) => previous !== undefined` \u2014 is `initial: false`. This rule does not guess between them. The word `when` is untouched at a feature and at `scope.while`; at a contribution it is now `enabled` (D379, D411).",eventBackpressure:'`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: "latest"` for one waiting place that the newest payload takes, or the record \u2014 `{ pending, by }`, `{ pending, capacity, overflow }` \u2014 for the rest (D374).',fromExternal:"`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).",navigatorPolicy:"`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data \u2014 a word when it has no parameters, a record when it has them (D411).",resourceActivity:"`ResourceActivity` is retired at both entries that spelled it. From `@opetope/devtools` it is `ResourceNodeActivity` (D411). From `@opetope/core` it is gone: a state answers what failed and what is working in two fields of its own, so read `state.failure` and `state.pending` instead of a union that flattened them (D462).",resourceDisposer:"`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell \u2014 `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).",resourceInvalidate:"`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means \xABmark stale, keep the data\xBB, so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).",runtimeInternalEntry:"`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).",scopeKeyed:"`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required \u2014 so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).",stateActivity:"No `ResourceState` carries `activity` any more: the union said what a channel was doing and what had failed in one word. Read `state.failure` \u2014 `undefined` is \xABnothing failed\xBB, and otherwise `channel` names the channel that died and `error` carries its failure \u2014 and `state.pending` for the channel with work in flight. Both are declared on every member, so neither needs a narrowing by `kind` first (D462).",useCommands:"`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388)."},schema:[],type:"problem"},name:"no-retired-vocabulary"});export{J as noRetiredVocabulary};
2
2
  //# sourceMappingURL=no-retired-vocabulary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"no-retired-vocabulary.js","sources":["../../src/rules/no-retired-vocabulary.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty, propertyName } from '../ast';\nimport { isContextFunction, methodPath } from '../declaration-ingress';\nimport { isApi, isFeatureSection, localValue } from '../model-bindings';\nimport {\n isCommandOutcome,\n isLoadingActivity,\n isResourceHook,\n isResourceValue,\n isSurfaceValue,\n} from '../retired-vocabulary';\nimport { createRule } from '../rule';\n\ntype MessageIds =\n | 'commandLane'\n | 'commandLatest'\n | 'commandName'\n | 'commandOnce'\n | 'commandOutcomeStatus'\n | 'commandPolicy'\n | 'commandQueueBy'\n | 'conditionFrom'\n | 'contextCall'\n | 'contextCalls'\n | 'contributionWhen'\n | 'deliveryKey'\n | 'deliveryKind'\n | 'deliveryReject'\n | 'devtoolsResourceActivity'\n | 'effectOnDispose'\n | 'effectSkipInitial'\n | 'effectWhen'\n | 'eventBackpressure'\n | 'fromExternal'\n | 'loadingActivity'\n | 'navigatorPolicy'\n | 'resourceDisposer'\n | 'resourceInvalidate'\n | 'runtimeInternalEntry'\n | 'scopeKeyed'\n | 'useCommands';\n\n/**\n * One name a package no longer exports, and the word that carries its meaning now. D411: `module` is written where\n * the name is retired at one entry and live at another — `ResourceActivity` is still what `@opetope/core` calls the\n * background work of a `ready` state, and `ApplicationConditionBinding` is still the name, on the entry an author\n * is meant to take it from.\n */\ntype Retired = { readonly messageId: MessageIds; readonly module?: string; readonly replacement: string };\n/**\n * Where one node keeps its modifiers, and which retired words live there. `at` holds every position the record is\n * written in across the two surfaces — `ctx.command(run, options)` and `own.command(within, run, options)` — and\n * nothing else at those positions is an object literal, so one list answers both. `carries` names the members whose\n * own record is read in turn: a delivery policy is a record inside a record.\n */\ntype Modifiers = {\n readonly at: readonly number[];\n readonly carries: readonly string[];\n /** D411: a word of a carried record whose **value** was retired, not its key: `overflow: 'reject'` is one. */\n readonly carriedValues?: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>;\n /** D459: the same fact one record higher — a word retired as the **value** of a field of this record itself. */\n readonly values?: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>;\n readonly words: ReadonlyMap<string, MessageIds>;\n};\n\n/** D387, D389: the four words the options record of a command declaration and of a selection retired. */\nconst COMMAND_WORDS = new Map<string, MessageIds>([\n ['lane', 'commandLane'],\n ['once', 'commandOnce'],\n ['policy', 'commandPolicy'],\n ['queueBy', 'commandQueueBy'],\n]);\n/** D459: the one value of that record that moved — the waiting place is written as the record that names it. */\nconst COMMAND_VALUES = new Map<string, ReadonlyMap<string, MessageIds>>([\n ['concurrency', new Map<string, MessageIds>([['latest', 'commandLatest']])],\n]);\n/** D379: the three modifiers of an effect run. */\nconst EFFECT_WORDS = new Map<string, MessageIds>([\n ['onDispose', 'effectOnDispose'],\n ['skipInitial', 'effectSkipInitial'],\n ['when', 'effectWhen'],\n]);\nconst EVENT_WORDS = new Map<string, MessageIds>([['backpressure', 'eventBackpressure']]);\n/** D374: the two fields inside the record, whichever of the two words the record itself is still written under. */\nconst DELIVERY_WORDS = new Map<string, MessageIds>([\n ['key', 'deliveryKey'],\n ['kind', 'deliveryKind'],\n]);\nconst NO_WORDS = new Map<string, MessageIds>();\nconst DELIVERY = ['backpressure', 'delivery'];\n/** D411: the one field of a delivery whose value moved: a full keyed capacity answers `drop`, not `reject`. */\nconst LIVE_DELIVERY_VALUES = new Map<string, ReadonlyMap<string, MessageIds>>([\n ['overflow', new Map<string, MessageIds>([['reject', 'deliveryReject']])],\n]);\n/** D411: the words a contribution writes beside its value, on the `provides` section that declares it. */\nconst CONTRIBUTION_WORDS = new Map<string, MessageIds>([['when', 'contributionWhen']]);\nconst CONTRIBUTION_RECORDS = new Map<string, Modifiers>([\n ['pipe', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n ['register', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n ['slot', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n]);\n/** D411: the record `defineCondition` takes second; the word it named its source by is the one that moved. */\nconst CONDITION_WORDS = new Map<string, MessageIds>([['from', 'conditionFrom']]);\n\nconst OPTION_RECORDS = new Map<string, Modifiers>([\n ['call', { at: [1, 2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['calls', { at: [2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['command', { at: [1, 2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['effect', { at: [2], carries: [], words: EFFECT_WORDS }],\n ['event', { at: [3], carries: DELIVERY, words: EVENT_WORDS }],\n ['resource.live', { at: [0], carriedValues: LIVE_DELIVERY_VALUES, carries: DELIVERY, words: NO_WORDS }],\n ['select', { at: [2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n]);\n\n/** D375, D411, D415: the members whose name is retired, each with the object it has to have been read on. */\nconst RETIRED_MEMBERS = new Map<string, MessageIds>([\n ['activity', 'loadingActivity'],\n ['invalidate', 'resourceInvalidate'],\n ['policy', 'navigatorPolicy'],\n ['status', 'commandOutcomeStatus'],\n]);\n\n/** D378, D385, D388: the methods themselves, named on a declaring context of this library. */\nconst RETIRED_METHODS = new Map<string, MessageIds>([\n ['call', 'contextCall'],\n ['calls', 'contextCalls'],\n ['scope.keyed', 'scopeKeyed'],\n]);\n\n/** D380, D385, D388: the names an `@opetope/*` entry no longer exports. The module itself is the proof. */\nconst RETIRED_IMPORTS = new Map<string, Retired>([\n [\n 'ApplicationConditionBinding',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n /** D411, D415: the entry retired three copies, and a table that held one left two migrations unnamed. */\n [\n 'ApplicationExecution',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n [\n 'ApplicationImportBinding',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n ['Call', { messageId: 'commandName', replacement: 'Command' }],\n ['CallError', { messageId: 'commandName', replacement: 'CommandError' }],\n ['CallInputArgs', { messageId: 'commandName', replacement: 'CommandInputArgs' }],\n ['CallTimeoutError', { messageId: 'commandName', replacement: 'CommandTimeoutError' }],\n ['NavigatorPolicy', { messageId: 'navigatorPolicy', replacement: 'NavigatorCatalogue' }],\n [\n 'ResourceActivity',\n { messageId: 'devtoolsResourceActivity', module: '@opetope/devtools', replacement: 'ResourceNodeActivity' },\n ],\n ['ResourceDisposer', { messageId: 'resourceDisposer', replacement: 'Disposer' }],\n ['RunCallOptions', { messageId: 'commandName', replacement: 'RunCommandOptions' }],\n ['externalReadable', { messageId: 'fromExternal', replacement: 'fromExternal' }],\n ['runCall', { messageId: 'commandName', replacement: 'runCommand' }],\n ['useCommands', { messageId: 'useCommands', replacement: 'useModel' }],\n]);\n\n/** What one call of a declaring context is, as far as this rule reads it: a retired word, retired modifiers, both. */\ntype Declared = { readonly modifiers: Modifiers | undefined; readonly word: MessageIds | undefined };\n\n/** The word a callee ends in: `keyed` of `own.scope.keyed`, or the plain name a destructured member is called by. */\nfunction calleeWord(callee: TSESTree.Node): TSESTree.Node {\n return callee.type === AST_NODE_TYPES.MemberExpression ? callee.property : callee;\n}\n\n/** The name an import carries in the module that exports it, past the local alias this file gave it. */\nfunction importedName(specifier: TSESTree.ImportSpecifier): string {\n return specifier.imported.type === AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;\n}\n\nconst noRetiredVocabulary = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n\n /**\n * The shape decides first and the context second: a call whose method this rule has nothing to say about never\n * pays for resolving the parameter it was written on (D309, D323).\n */\n const declaredCall = (node: TSESTree.CallExpression): Declared | undefined => {\n const named = methodPath(source, node.callee);\n\n if (named === undefined) return undefined;\n\n const declared: Declared = {\n modifiers: OPTION_RECORDS.get(named.method),\n word: RETIRED_METHODS.get(named.method),\n };\n\n if (declared.modifiers === undefined && declared.word === undefined) return undefined;\n\n return isContextFunction(source, named.fn) ? declared : undefined;\n };\n /** The record written at one of the positions this node takes its modifiers in, past the name it was given. */\n const recordAt = (node: TSESTree.CallExpression, position: number): TSESTree.ObjectExpression | undefined => {\n const argument = node.arguments[position];\n\n if (argument === undefined || argument.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n const value = localValue(source, argument);\n\n return value.type === AST_NODE_TYPES.ObjectExpression ? value : undefined;\n };\n /**\n * D411: a contribution is declared on `provides`, not on `own`, so the section that proves it is its own. The\n * shape decides first here too: nothing resolves a section for a method this rule has nothing to say about.\n */\n const contributionCall = (node: TSESTree.CallExpression): Modifiers | undefined => {\n const named = methodPath(source, node.callee);\n const modifiers = named === undefined ? undefined : CONTRIBUTION_RECORDS.get(named.method);\n\n if (named === undefined || modifiers === undefined) return undefined;\n\n return isFeatureSection(source, named.fn, 'provides', true) ? modifiers : undefined;\n };\n /** D411: `defineCondition(id, options)` is a name this package exports, so the import is what proves it. */\n const conditionRecord = (node: TSESTree.CallExpression): TSESTree.ObjectExpression | undefined => {\n if (!isApi(source, node.callee, '@opetope/runtime', 'defineCondition', true)) return undefined;\n\n return recordAt(node, 1);\n };\n const reportRecord = (record: TSESTree.ObjectExpression, words: ReadonlyMap<string, MessageIds>): void => {\n for (const property of record.properties) {\n if (property.type !== AST_NODE_TYPES.Property) continue;\n\n const name = propertyName(property);\n const messageId = name === undefined ? undefined : words.get(name);\n\n if (messageId !== undefined) context.report({ messageId, node: property.key });\n }\n };\n /** D411: the word this property was written with, where that word is a retired value of a known field. */\n const retiredValue = (\n property: TSESTree.Property,\n values: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>,\n ): MessageIds | undefined => {\n const name = propertyName(property);\n const written = name === undefined ? undefined : values.get(name);\n\n if (written === undefined) return undefined;\n\n const value = localValue(source, property.value);\n\n return value.type === AST_NODE_TYPES.Literal && typeof value.value === 'string'\n ? written.get(value.value)\n : undefined;\n };\n /** D411: the value a field of a carried record was written with, where that value is the retired word. */\n const reportValues = (\n record: TSESTree.ObjectExpression,\n values: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>,\n ): void => {\n for (const property of record.properties) {\n if (property.type !== AST_NODE_TYPES.Property) continue;\n\n const messageId = retiredValue(property, values);\n\n if (messageId !== undefined) context.report({ messageId, node: property.value });\n }\n };\n const reportCarried = (record: TSESTree.ObjectExpression, carrier: string, modifiers: Modifiers): void => {\n const property = findProperty(record, carrier);\n const carried = property === undefined ? undefined : localValue(source, property.value);\n\n if (carried?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n reportRecord(carried, DELIVERY_WORDS);\n\n if (modifiers.carriedValues !== undefined) reportValues(carried, modifiers.carriedValues);\n };\n const reportModifiers = (node: TSESTree.CallExpression, modifiers: Modifiers): void => {\n for (const position of modifiers.at) {\n const record = recordAt(node, position);\n\n if (record === undefined) continue;\n\n reportRecord(record, modifiers.words);\n\n if (modifiers.values !== undefined) reportValues(record, modifiers.values);\n\n for (const carrier of modifiers.carries) reportCarried(record, carrier, modifiers);\n }\n };\n /** Both objects that used to answer `invalidate`: the Resource itself, and the record `useResource` builds. */\n const holdsVerbs = (node: TSESTree.Node): boolean => isResourceValue(source, node) || isResourceHook(source, node);\n /** The object each retired member has to be read on, asked only once the name has already matched. */\n const memberOwner = (node: TSESTree.MemberExpression, messageId: MessageIds): boolean => {\n if (messageId === 'resourceInvalidate') return holdsVerbs(node.object);\n\n if (messageId === 'loadingActivity') return isLoadingActivity(source, node);\n\n if (messageId === 'navigatorPolicy') return isSurfaceValue(source, node.object);\n\n return isCommandOutcome(source, node.object);\n };\n /**\n * The four members this rule reads off a value it can resolve (D415 added `surface.policy`, which the message\n * of `NavigatorPolicy` already named and the rule did not report). The name decides first and the resolution\n * second, so a member this rule has nothing to say about never pays for the object it was read on (D309, D323).\n */\n const retiredMember = (node: TSESTree.MemberExpression, name: string): MessageIds | undefined => {\n const messageId = RETIRED_MEMBERS.get(name);\n\n return messageId !== undefined && memberOwner(node, messageId) ? messageId : undefined;\n };\n\n return {\n CallExpression(node): void {\n const contribution = contributionCall(node);\n\n if (contribution !== undefined) reportModifiers(node, contribution);\n\n const condition = conditionRecord(node);\n\n if (condition !== undefined) reportRecord(condition, CONDITION_WORDS);\n\n const declared = declaredCall(node);\n\n if (declared === undefined) return;\n\n if (declared.word !== undefined) context.report({ messageId: declared.word, node: calleeWord(node.callee) });\n\n if (declared.modifiers !== undefined) reportModifiers(node, declared.modifiers);\n },\n ImportDeclaration(node): void {\n if (!node.source.value.startsWith('@opetope/')) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) continue;\n\n const word = importedName(specifier);\n const retired = RETIRED_IMPORTS.get(word);\n\n if (retired !== undefined && (retired.module === undefined || retired.module === node.source.value)) {\n context.report({\n data: { replacement: retired.replacement, word },\n messageId: retired.messageId,\n node: specifier.imported,\n });\n }\n }\n },\n MemberExpression(node): void {\n if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier) return;\n\n const messageId = retiredMember(node, node.property.name);\n\n if (messageId !== undefined) context.report({ messageId, node: node.property });\n },\n Property(node): void {\n if (propertyName(node) !== 'invalidate' || node.parent.type !== AST_NODE_TYPES.ObjectPattern) return;\n\n const declarator = node.parent.parent;\n\n if (declarator.type !== AST_NODE_TYPES.VariableDeclarator || declarator.init === null) return;\n\n if (holdsVerbs(declarator.init)) context.report({ messageId: 'resourceInvalidate', node: node.key });\n },\n };\n },\n meta: {\n docs: {\n description:\n 'A word the D365–D411 wave or a decision after it retired is named with its replacement, never rewritten.',\n },\n messages: {\n commandLane:\n '`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: \"latest\" }` where `policy: \"latest\"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',\n commandLatest:\n '`concurrency: \"latest\"` is now `concurrency: { pending: \"latest\" }`, and `{ lane, pending: \"latest\" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',\n commandName:\n '`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).',\n commandOnce:\n '`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).',\n commandOutcomeStatus:\n '`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged — `{ kind: \"ok\", value }`, `{ kind: \"failed\", error }`, `{ kind: \"cancelled\", reason }` (D411).',\n commandPolicy:\n '`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: \"latest\" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value — `{ by }`, `{ by, lane }` or `{ lane, pending: \"latest\" }` — so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',\n commandQueueBy:\n '`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).',\n conditionFrom:\n \"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads — `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).\",\n contextCall:\n '`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` — and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => …`), a constant names it (`const refill: Command<Refill, void> = ctx.command(…)`), and the annotation of that context — `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature — is what is left; a feature has the last two only. `command<Deposit>(…)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).',\n contextCalls:\n '`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, \"name\")` answers one command and `select(source, [\"a\", \"b\"])` a record of them (D388).',\n contributionWhen:\n '`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the other mechanism — the declared `Condition`s of `defineFeature({ when })`, which the application resolves before anything exists. `scope.while` keeps its own `when` (D378, D411).',\n deliveryKey:\n 'Inside a delivery record `key` is now `by`: `{ pending: \"latest\", by }` keeps one waiting place per key instead of one for the whole node (D374).',\n deliveryKind:\n 'Inside a delivery record `kind` is now `pending`: `kind: \"latest\"` is `pending: \"latest\"`, and `kind: \"queue\"` is `pending: \"all\"` — the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',\n deliveryReject:\n '`overflow: \"reject\"` is now `overflow: \"drop\"`: one word at both nodes for «the new one does not enter and `emit` answers `false`». `\"flush-oldest\"` is unchanged and still the default of a keyed delivery (D369, D411).',\n devtoolsResourceActivity:\n '`ResourceActivity` of `@opetope/devtools` is now `ResourceNodeActivity`: `@opetope/core` exports a `ResourceActivity` of its own — the union a `ready` state carries — and one name carried two unrelated types on two public entries. Import the core one under its own name (D411).',\n effectOnDispose:\n '`onDispose` is now `finalize`: an effect has two releases — the disposer a run returns, and the one the node itself gets once — and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).',\n effectSkipInitial:\n '`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite — which is why this rule names it and leaves the rewrite to you (D379).',\n effectWhen:\n '`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => …`; a predicate whose whole job is to refuse the first run — `(_current, previous) => previous !== undefined` — is `initial: false`. This rule does not guess between them. The word `when` is untouched at a feature and at `scope.while`; at a contribution it is now `enabled` (D379, D411).',\n eventBackpressure:\n '`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: \"latest\"` for one waiting place that the newest payload takes, or the record — `{ pending, by }`, `{ pending, capacity, overflow }` — for the rest (D374).',\n fromExternal:\n '`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).',\n loadingActivity:\n 'A `loading` state carries `failure`, not `activity`: read `state.failure` — `undefined` is «both channels are still viable», and otherwise `channel` names the one that died terminally and `error` carries its failure. A `ready` state still carries `activity` whole, so this is one branch losing the field and not the field being renamed (D375).',\n navigatorPolicy:\n '`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data — a word when it has no parameters, a record when it has them (D411).',\n resourceDisposer:\n '`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell — `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).',\n resourceInvalidate:\n '`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means «mark stale, keep the data», so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).',\n runtimeInternalEntry:\n '`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).',\n scopeKeyed:\n '`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required — so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).',\n useCommands:\n '`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-retired-vocabulary',\n});\n\nexport { noRetiredVocabulary };\n"],"names":["COMMAND_WORDS","COMMAND_VALUES","EFFECT_WORDS","EVENT_WORDS","DELIVERY_WORDS","NO_WORDS","DELIVERY","LIVE_DELIVERY_VALUES","CONTRIBUTION_WORDS","CONTRIBUTION_RECORDS","CONDITION_WORDS","OPTION_RECORDS","RETIRED_MEMBERS","RETIRED_METHODS","RETIRED_IMPORTS","calleeWord","callee","AST_NODE_TYPES","importedName","specifier","noRetiredVocabulary","createRule","context","source","declaredCall","node","named","methodPath","declared","isContextFunction","recordAt","position","argument","value","localValue","contributionCall","modifiers","isFeatureSection","conditionRecord","isApi","reportRecord","record","words","property","name","propertyName","messageId","retiredValue","values","written","reportValues","reportCarried","carrier","findProperty","carried","reportModifiers","holdsVerbs","isResourceValue","isResourceHook","memberOwner","isLoadingActivity","isSurfaceValue","isCommandOutcome","retiredMember","contribution","condition","word","retired","declarator"],"mappings":"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,CACX,GAAIoB,EAAapB,CAAI,IAAM,cAAgBA,EAAK,OAAO,OAASR,EAAe,cAAe,OAE9F,MAAMmD,EAAa3C,EAAK,OAAO,OAE3B2C,EAAW,OAASnD,EAAe,oBAAsBmD,EAAW,OAAS,MAE7EZ,EAAWY,EAAW,IAAI,GAAG9C,EAAQ,OAAO,CAAE,UAAW,qBAAsB,KAAMG,EAAK,IAAK,CACrG,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YACE,+GACH,EACD,SAAU,CACR,YACE,6TACF,cACE,0XACF,YACE,kPACF,YACE,oOACF,qBACE,8WACF,cACE,saACF,eACE,6JACF,cACE,yTACF,YACE,04BACF,aACE,8RACF,iBACE,+aACF,YACE,oJACF,aACE,8RACF,eACE,kOACF,yBACE,kSACF,gBACE,kQACF,kBACE,wTACF,WACE,wdACF,kBACE,wQACF,aACE,6MACF,gBACE,qWACF,gBACE,kPACF,iBACE,+OACF,mBACE,yUACF,qBACE,uNACF,WACE,4TACF,YACE,+KACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,uBACP,CAAA"}
1
+ {"version":3,"file":"no-retired-vocabulary.js","sources":["../../src/rules/no-retired-vocabulary.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty, propertyName } from '../ast';\nimport { isContextFunction, methodPath } from '../declaration-ingress';\nimport { isApi, isFeatureSection, localValue } from '../model-bindings';\nimport {\n isCommandOutcome,\n isResourceHook,\n isResourceValue,\n isStateActivity,\n isSurfaceValue,\n} from '../retired-vocabulary';\nimport { createRule } from '../rule';\n\ntype MessageIds =\n | 'commandLane'\n | 'commandLatest'\n | 'commandName'\n | 'commandOnce'\n | 'commandOutcomeStatus'\n | 'commandPolicy'\n | 'commandQueueBy'\n | 'conditionFrom'\n | 'contextCall'\n | 'contextCalls'\n | 'contributionWhen'\n | 'deliveryKey'\n | 'deliveryKind'\n | 'deliveryReject'\n | 'effectOnDispose'\n | 'effectSkipInitial'\n | 'effectWhen'\n | 'eventBackpressure'\n | 'fromExternal'\n | 'navigatorPolicy'\n | 'resourceActivity'\n | 'resourceDisposer'\n | 'resourceInvalidate'\n | 'runtimeInternalEntry'\n | 'scopeKeyed'\n | 'stateActivity'\n | 'useCommands';\n\n/**\n * One name a package no longer exports, and the word that carries its meaning now. D411: `module` is written where\n * the name is retired at one entry and live at another — `ApplicationConditionBinding` is still the name, on the\n * entry an author is meant to take it from. D462 took the scope off `ResourceActivity`: the name is now retired at\n * both entries it was ever written from, so asking the module would only let one of them through.\n */\ntype Retired = { readonly messageId: MessageIds; readonly module?: string; readonly replacement: string };\n/**\n * Where one node keeps its modifiers, and which retired words live there. `at` holds every position the record is\n * written in across the two surfaces — `ctx.command(run, options)` and `own.command(within, run, options)` — and\n * nothing else at those positions is an object literal, so one list answers both. `carries` names the members whose\n * own record is read in turn: a delivery policy is a record inside a record.\n */\ntype Modifiers = {\n readonly at: readonly number[];\n readonly carries: readonly string[];\n /** D411: a word of a carried record whose **value** was retired, not its key: `overflow: 'reject'` is one. */\n readonly carriedValues?: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>;\n /** D459: the same fact one record higher — a word retired as the **value** of a field of this record itself. */\n readonly values?: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>;\n readonly words: ReadonlyMap<string, MessageIds>;\n};\n\n/** D387, D389: the four words the options record of a command declaration and of a selection retired. */\nconst COMMAND_WORDS = new Map<string, MessageIds>([\n ['lane', 'commandLane'],\n ['once', 'commandOnce'],\n ['policy', 'commandPolicy'],\n ['queueBy', 'commandQueueBy'],\n]);\n/** D459: the one value of that record that moved — the waiting place is written as the record that names it. */\nconst COMMAND_VALUES = new Map<string, ReadonlyMap<string, MessageIds>>([\n ['concurrency', new Map<string, MessageIds>([['latest', 'commandLatest']])],\n]);\n/** D379: the three modifiers of an effect run. */\nconst EFFECT_WORDS = new Map<string, MessageIds>([\n ['onDispose', 'effectOnDispose'],\n ['skipInitial', 'effectSkipInitial'],\n ['when', 'effectWhen'],\n]);\nconst EVENT_WORDS = new Map<string, MessageIds>([['backpressure', 'eventBackpressure']]);\n/** D374: the two fields inside the record, whichever of the two words the record itself is still written under. */\nconst DELIVERY_WORDS = new Map<string, MessageIds>([\n ['key', 'deliveryKey'],\n ['kind', 'deliveryKind'],\n]);\nconst NO_WORDS = new Map<string, MessageIds>();\nconst DELIVERY = ['backpressure', 'delivery'];\n/** D411: the one field of a delivery whose value moved: a full keyed capacity answers `drop`, not `reject`. */\nconst LIVE_DELIVERY_VALUES = new Map<string, ReadonlyMap<string, MessageIds>>([\n ['overflow', new Map<string, MessageIds>([['reject', 'deliveryReject']])],\n]);\n/** D411: the words a contribution writes beside its value, on the `provides` section that declares it. */\nconst CONTRIBUTION_WORDS = new Map<string, MessageIds>([['when', 'contributionWhen']]);\nconst CONTRIBUTION_RECORDS = new Map<string, Modifiers>([\n ['pipe', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n ['register', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n ['slot', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n]);\n/** D411: the record `defineCondition` takes second; the word it named its source by is the one that moved. */\nconst CONDITION_WORDS = new Map<string, MessageIds>([['from', 'conditionFrom']]);\n\nconst OPTION_RECORDS = new Map<string, Modifiers>([\n ['call', { at: [1, 2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['calls', { at: [2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['command', { at: [1, 2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['effect', { at: [2], carries: [], words: EFFECT_WORDS }],\n ['event', { at: [3], carries: DELIVERY, words: EVENT_WORDS }],\n ['resource.live', { at: [0], carriedValues: LIVE_DELIVERY_VALUES, carries: DELIVERY, words: NO_WORDS }],\n ['select', { at: [2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n]);\n\n/** D411, D415, D462: the members whose name is retired, each with the object it has to have been read on. */\nconst RETIRED_MEMBERS = new Map<string, MessageIds>([\n ['activity', 'stateActivity'],\n ['invalidate', 'resourceInvalidate'],\n ['policy', 'navigatorPolicy'],\n ['status', 'commandOutcomeStatus'],\n]);\n\n/** D378, D385, D388: the methods themselves, named on a declaring context of this library. */\nconst RETIRED_METHODS = new Map<string, MessageIds>([\n ['call', 'contextCall'],\n ['calls', 'contextCalls'],\n ['scope.keyed', 'scopeKeyed'],\n]);\n\n/** D380, D385, D388: the names an `@opetope/*` entry no longer exports. The module itself is the proof. */\nconst RETIRED_IMPORTS = new Map<string, Retired>([\n [\n 'ApplicationConditionBinding',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n /** D411, D415: the entry retired three copies, and a table that held one left two migrations unnamed. */\n [\n 'ApplicationExecution',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n [\n 'ApplicationImportBinding',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n ['Call', { messageId: 'commandName', replacement: 'Command' }],\n ['CallError', { messageId: 'commandName', replacement: 'CommandError' }],\n ['CallInputArgs', { messageId: 'commandName', replacement: 'CommandInputArgs' }],\n ['CallTimeoutError', { messageId: 'commandName', replacement: 'CommandTimeoutError' }],\n ['NavigatorPolicy', { messageId: 'navigatorPolicy', replacement: 'NavigatorCatalogue' }],\n ['ResourceActivity', { messageId: 'resourceActivity', replacement: 'ResourceNodeActivity' }],\n ['ResourceDisposer', { messageId: 'resourceDisposer', replacement: 'Disposer' }],\n ['RunCallOptions', { messageId: 'commandName', replacement: 'RunCommandOptions' }],\n ['externalReadable', { messageId: 'fromExternal', replacement: 'fromExternal' }],\n ['runCall', { messageId: 'commandName', replacement: 'runCommand' }],\n ['useCommands', { messageId: 'useCommands', replacement: 'useModel' }],\n]);\n\n/** What one call of a declaring context is, as far as this rule reads it: a retired word, retired modifiers, both. */\ntype Declared = { readonly modifiers: Modifiers | undefined; readonly word: MessageIds | undefined };\n\n/** The word a callee ends in: `keyed` of `own.scope.keyed`, or the plain name a destructured member is called by. */\nfunction calleeWord(callee: TSESTree.Node): TSESTree.Node {\n return callee.type === AST_NODE_TYPES.MemberExpression ? callee.property : callee;\n}\n\n/** The name an import carries in the module that exports it, past the local alias this file gave it. */\nfunction importedName(specifier: TSESTree.ImportSpecifier): string {\n return specifier.imported.type === AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;\n}\n\nconst noRetiredVocabulary = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n\n /**\n * The shape decides first and the context second: a call whose method this rule has nothing to say about never\n * pays for resolving the parameter it was written on (D309, D323).\n */\n const declaredCall = (node: TSESTree.CallExpression): Declared | undefined => {\n const named = methodPath(source, node.callee);\n\n if (named === undefined) return undefined;\n\n const declared: Declared = {\n modifiers: OPTION_RECORDS.get(named.method),\n word: RETIRED_METHODS.get(named.method),\n };\n\n if (declared.modifiers === undefined && declared.word === undefined) return undefined;\n\n return isContextFunction(source, named.fn) ? declared : undefined;\n };\n /** The record written at one of the positions this node takes its modifiers in, past the name it was given. */\n const recordAt = (node: TSESTree.CallExpression, position: number): TSESTree.ObjectExpression | undefined => {\n const argument = node.arguments[position];\n\n if (argument === undefined || argument.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n const value = localValue(source, argument);\n\n return value.type === AST_NODE_TYPES.ObjectExpression ? value : undefined;\n };\n /**\n * D411: a contribution is declared on `provides`, not on `own`, so the section that proves it is its own. The\n * shape decides first here too: nothing resolves a section for a method this rule has nothing to say about.\n */\n const contributionCall = (node: TSESTree.CallExpression): Modifiers | undefined => {\n const named = methodPath(source, node.callee);\n const modifiers = named === undefined ? undefined : CONTRIBUTION_RECORDS.get(named.method);\n\n if (named === undefined || modifiers === undefined) return undefined;\n\n return isFeatureSection(source, named.fn, 'provides', true) ? modifiers : undefined;\n };\n /** D411: `defineCondition(id, options)` is a name this package exports, so the import is what proves it. */\n const conditionRecord = (node: TSESTree.CallExpression): TSESTree.ObjectExpression | undefined => {\n if (!isApi(source, node.callee, '@opetope/runtime', 'defineCondition', true)) return undefined;\n\n return recordAt(node, 1);\n };\n const reportRecord = (record: TSESTree.ObjectExpression, words: ReadonlyMap<string, MessageIds>): void => {\n for (const property of record.properties) {\n if (property.type !== AST_NODE_TYPES.Property) continue;\n\n const name = propertyName(property);\n const messageId = name === undefined ? undefined : words.get(name);\n\n if (messageId !== undefined) context.report({ messageId, node: property.key });\n }\n };\n /** D411: the word this property was written with, where that word is a retired value of a known field. */\n const retiredValue = (\n property: TSESTree.Property,\n values: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>,\n ): MessageIds | undefined => {\n const name = propertyName(property);\n const written = name === undefined ? undefined : values.get(name);\n\n if (written === undefined) return undefined;\n\n const value = localValue(source, property.value);\n\n return value.type === AST_NODE_TYPES.Literal && typeof value.value === 'string'\n ? written.get(value.value)\n : undefined;\n };\n /** D411: the value a field of a carried record was written with, where that value is the retired word. */\n const reportValues = (\n record: TSESTree.ObjectExpression,\n values: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>,\n ): void => {\n for (const property of record.properties) {\n if (property.type !== AST_NODE_TYPES.Property) continue;\n\n const messageId = retiredValue(property, values);\n\n if (messageId !== undefined) context.report({ messageId, node: property.value });\n }\n };\n const reportCarried = (record: TSESTree.ObjectExpression, carrier: string, modifiers: Modifiers): void => {\n const property = findProperty(record, carrier);\n const carried = property === undefined ? undefined : localValue(source, property.value);\n\n if (carried?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n reportRecord(carried, DELIVERY_WORDS);\n\n if (modifiers.carriedValues !== undefined) reportValues(carried, modifiers.carriedValues);\n };\n const reportModifiers = (node: TSESTree.CallExpression, modifiers: Modifiers): void => {\n for (const position of modifiers.at) {\n const record = recordAt(node, position);\n\n if (record === undefined) continue;\n\n reportRecord(record, modifiers.words);\n\n if (modifiers.values !== undefined) reportValues(record, modifiers.values);\n\n for (const carrier of modifiers.carries) reportCarried(record, carrier, modifiers);\n }\n };\n /** Both objects that used to answer `invalidate`: the Resource itself, and the record `useResource` builds. */\n const holdsVerbs = (node: TSESTree.Node): boolean => isResourceValue(source, node) || isResourceHook(source, node);\n /** The object each retired member has to be read on, asked only once the name has already matched. */\n const memberOwner = (node: TSESTree.MemberExpression, messageId: MessageIds): boolean => {\n if (messageId === 'resourceInvalidate') return holdsVerbs(node.object);\n\n if (messageId === 'stateActivity') return isStateActivity(source, node);\n\n if (messageId === 'navigatorPolicy') return isSurfaceValue(source, node.object);\n\n return isCommandOutcome(source, node.object);\n };\n /**\n * The four members this rule reads off a value it can resolve (D415 added `surface.policy`, which the message\n * of `NavigatorPolicy` already named and the rule did not report). The name decides first and the resolution\n * second, so a member this rule has nothing to say about never pays for the object it was read on (D309, D323).\n */\n const retiredMember = (node: TSESTree.MemberExpression, name: string): MessageIds | undefined => {\n const messageId = RETIRED_MEMBERS.get(name);\n\n return messageId !== undefined && memberOwner(node, messageId) ? messageId : undefined;\n };\n\n return {\n CallExpression(node): void {\n const contribution = contributionCall(node);\n\n if (contribution !== undefined) reportModifiers(node, contribution);\n\n const condition = conditionRecord(node);\n\n if (condition !== undefined) reportRecord(condition, CONDITION_WORDS);\n\n const declared = declaredCall(node);\n\n if (declared === undefined) return;\n\n if (declared.word !== undefined) context.report({ messageId: declared.word, node: calleeWord(node.callee) });\n\n if (declared.modifiers !== undefined) reportModifiers(node, declared.modifiers);\n },\n ImportDeclaration(node): void {\n if (!node.source.value.startsWith('@opetope/')) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) continue;\n\n const word = importedName(specifier);\n const retired = RETIRED_IMPORTS.get(word);\n\n if (retired !== undefined && (retired.module === undefined || retired.module === node.source.value)) {\n context.report({\n data: { replacement: retired.replacement, word },\n messageId: retired.messageId,\n node: specifier.imported,\n });\n }\n }\n },\n MemberExpression(node): void {\n if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier) return;\n\n const messageId = retiredMember(node, node.property.name);\n\n if (messageId !== undefined) context.report({ messageId, node: node.property });\n },\n Property(node): void {\n if (propertyName(node) !== 'invalidate' || node.parent.type !== AST_NODE_TYPES.ObjectPattern) return;\n\n const declarator = node.parent.parent;\n\n if (declarator.type !== AST_NODE_TYPES.VariableDeclarator || declarator.init === null) return;\n\n if (holdsVerbs(declarator.init)) context.report({ messageId: 'resourceInvalidate', node: node.key });\n },\n };\n },\n meta: {\n docs: {\n description:\n 'A word the D365–D411 wave or a decision after it retired is named with its replacement, never rewritten.',\n },\n messages: {\n commandLane:\n '`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: \"latest\" }` where `policy: \"latest\"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',\n commandLatest:\n '`concurrency: \"latest\"` is now `concurrency: { pending: \"latest\" }`, and `{ lane, pending: \"latest\" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',\n commandName:\n '`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).',\n commandOnce:\n '`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).',\n commandOutcomeStatus:\n '`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged — `{ kind: \"ok\", value }`, `{ kind: \"failed\", error }`, `{ kind: \"cancelled\", reason }` (D411).',\n commandPolicy:\n '`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: \"latest\" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value — `{ by }`, `{ by, lane }` or `{ lane, pending: \"latest\" }` — so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',\n commandQueueBy:\n '`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).',\n conditionFrom:\n \"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads — `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).\",\n contextCall:\n '`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` — and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => …`), a constant names it (`const refill: Command<Refill, void> = ctx.command(…)`), and the annotation of that context — `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature — is what is left; a feature has the last two only. `command<Deposit>(…)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).',\n contextCalls:\n '`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, \"name\")` answers one command and `select(source, [\"a\", \"b\"])` a record of them (D388).',\n contributionWhen:\n '`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the other mechanism — the declared `Condition`s of `defineFeature({ when })`, which the application resolves before anything exists. `scope.while` keeps its own `when` (D378, D411).',\n deliveryKey:\n 'Inside a delivery record `key` is now `by`: `{ pending: \"latest\", by }` keeps one waiting place per key instead of one for the whole node (D374).',\n deliveryKind:\n 'Inside a delivery record `kind` is now `pending`: `kind: \"latest\"` is `pending: \"latest\"`, and `kind: \"queue\"` is `pending: \"all\"` — the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',\n deliveryReject:\n '`overflow: \"reject\"` is now `overflow: \"drop\"`: one word at both nodes for «the new one does not enter and `emit` answers `false`». `\"flush-oldest\"` is unchanged and still the default of a keyed delivery (D369, D411).',\n effectOnDispose:\n '`onDispose` is now `finalize`: an effect has two releases — the disposer a run returns, and the one the node itself gets once — and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).',\n effectSkipInitial:\n '`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite — which is why this rule names it and leaves the rewrite to you (D379).',\n effectWhen:\n '`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => …`; a predicate whose whole job is to refuse the first run — `(_current, previous) => previous !== undefined` — is `initial: false`. This rule does not guess between them. The word `when` is untouched at a feature and at `scope.while`; at a contribution it is now `enabled` (D379, D411).',\n eventBackpressure:\n '`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: \"latest\"` for one waiting place that the newest payload takes, or the record — `{ pending, by }`, `{ pending, capacity, overflow }` — for the rest (D374).',\n fromExternal:\n '`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).',\n navigatorPolicy:\n '`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data — a word when it has no parameters, a record when it has them (D411).',\n resourceActivity:\n '`ResourceActivity` is retired at both entries that spelled it. From `@opetope/devtools` it is `ResourceNodeActivity` (D411). From `@opetope/core` it is gone: a state answers what failed and what is working in two fields of its own, so read `state.failure` and `state.pending` instead of a union that flattened them (D462).',\n resourceDisposer:\n '`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell — `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).',\n resourceInvalidate:\n '`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means «mark stale, keep the data», so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).',\n runtimeInternalEntry:\n '`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).',\n scopeKeyed:\n '`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required — so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).',\n stateActivity:\n 'No `ResourceState` carries `activity` any more: the union said what a channel was doing and what had failed in one word. Read `state.failure` — `undefined` is «nothing failed», and otherwise `channel` names the channel that died and `error` carries its failure — and `state.pending` for the channel with work in flight. Both are declared on every member, so neither needs a narrowing by `kind` first (D462).',\n useCommands:\n '`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-retired-vocabulary',\n});\n\nexport { noRetiredVocabulary };\n"],"names":["COMMAND_WORDS","COMMAND_VALUES","EFFECT_WORDS","EVENT_WORDS","DELIVERY_WORDS","NO_WORDS","DELIVERY","LIVE_DELIVERY_VALUES","CONTRIBUTION_WORDS","CONTRIBUTION_RECORDS","CONDITION_WORDS","OPTION_RECORDS","RETIRED_MEMBERS","RETIRED_METHODS","RETIRED_IMPORTS","calleeWord","callee","AST_NODE_TYPES","importedName","specifier","noRetiredVocabulary","createRule","context","source","declaredCall","node","named","methodPath","declared","isContextFunction","recordAt","position","argument","value","localValue","contributionCall","modifiers","isFeatureSection","conditionRecord","isApi","reportRecord","record","words","property","name","propertyName","messageId","retiredValue","values","written","reportValues","reportCarried","carrier","findProperty","carried","reportModifiers","holdsVerbs","isResourceValue","isResourceHook","memberOwner","isStateActivity","isSurfaceValue","isCommandOutcome","retiredMember","contribution","condition","word","retired","declarator"],"mappings":"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EAAapB,CAAI,IAAM,cAAgBA,EAAK,OAAO,OAASR,EAAe,cAAe,OAE9F,MAAMmD,EAAa3C,EAAK,OAAO,OAE3B2C,EAAW,OAASnD,EAAe,oBAAsBmD,EAAW,OAAS,MAE7EZ,EAAWY,EAAW,IAAI,GAAG9C,EAAQ,OAAO,CAAE,UAAW,qBAAsB,KAAMG,EAAK,IAAK,CACrG,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YACE,+GACH,EACD,SAAU,CACR,YACE,6TACF,cACE,0XACF,YACE,kPACF,YACE,oOACF,qBACE,8WACF,cACE,saACF,eACE,6JACF,cACE,yTACF,YACE,04BACF,aACE,8RACF,iBACE,+aACF,YACE,oJACF,aACE,8RACF,eACE,kOACF,gBACE,kQACF,kBACE,wTACF,WACE,wdACF,kBACE,wQACF,aACE,6MACF,gBACE,kPACF,iBACE,qUACF,iBACE,+OACF,mBACE,yUACF,qBACE,uNACF,WACE,4TACF,cACE,0aACF,YACE,+KACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,uBACP,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ declare const preferLazyBodyRecord: TSESLint.RuleModule<"preferRecord", [], unknown, TSESLint.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export { preferLazyBodyRecord };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as d,TSESLint as p}from"@typescript-eslint/utils";import{definitionOf as s}from"../model-bindings.js";import{bodyCallback as c,headerRecord as f,recordReplacement as m}from"../lazy-body-migration.js";import{createRule as y}from"../rule.js";function l(t,e){return e?.type===d.Identifier&&s(t,e)?.type===p.Scope.DefinitionType.ImportBinding}const u=y({create(t){const e=t.sourceCode;return{CallExpression(o){const r=c(e,o);if(r===void 0)return;const i=o.arguments[0];if(!l(e,i)&&f(e,i)===void 0)return;const n=m(e,o,r.fn,r.entries);t.report({node:r.fn,messageId:"preferRecord",fix:n===void 0?null:a=>a.replaceText(r.fn,n)})}}},meta:{docs:{description:"Prefer implementations keyed by a lazy header over repeated targets and priorities."},fixable:"code",messages:{preferRecord:"For a lazy header with fixed target keys, use `provides: { key: implementation }`; target and priority already belong to the header. Keep the callback when the header is dynamic or its target types require it (D464)."},schema:[],type:"suggestion"},name:"prefer-lazy-body-record"});export{u as preferLazyBodyRecord};
2
+ //# sourceMappingURL=prefer-lazy-body-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-lazy-body-record.js","sources":["../../src/rules/prefer-lazy-body-record.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils';\n\nimport { definitionOf } from '../model-bindings';\nimport { bodyCallback, headerRecord, recordReplacement } from '../lazy-body-migration';\nimport { createRule } from '../rule';\n\nfunction importedHeader(source: TSESLint.SourceCode, header: TSESTree.Node | undefined): boolean {\n return (\n header?.type === AST_NODE_TYPES.Identifier &&\n definitionOf(source, header)?.type === TSESLint.Scope.DefinitionType.ImportBinding\n );\n}\n\nconst preferLazyBodyRecord = createRule<[], 'preferRecord'>({\n create(context) {\n const source = context.sourceCode;\n return {\n CallExpression(call): void {\n const callback = bodyCallback(source, call);\n if (callback === undefined) return;\n const header = call.arguments[0];\n const imported = importedHeader(source, header);\n if (!imported && headerRecord(source, header) === undefined) return;\n const replacement = recordReplacement(source, call, callback.fn, callback.entries);\n context.report({\n node: callback.fn,\n messageId: 'preferRecord',\n fix: replacement === undefined ? null : fixer => fixer.replaceText(callback.fn, replacement),\n });\n },\n };\n },\n meta: {\n docs: { description: 'Prefer implementations keyed by a lazy header over repeated targets and priorities.' },\n fixable: 'code',\n messages: {\n preferRecord:\n 'For a lazy header with fixed target keys, use `provides: { key: implementation }`; target and priority already belong to the header. Keep the callback when the header is dynamic or its target types require it (D464).',\n },\n schema: [],\n type: 'suggestion',\n },\n name: 'prefer-lazy-body-record',\n});\n\nexport { preferLazyBodyRecord };\n"],"names":["importedHeader","source","header","AST_NODE_TYPES","definitionOf","TSESLint","preferLazyBodyRecord","createRule","context","call","callback","bodyCallback","headerRecord","replacement","recordReplacement","fixer"],"mappings":"sQAOA,SAASA,EAAeC,EAA6BC,EAAiC,CACpF,OACEA,GAAQ,OAASC,EAAe,YAChCC,EAAaH,EAAQC,CAAM,GAAG,OAASG,EAAS,MAAM,eAAe,aAEzE,CAEA,MAAMC,EAAuBC,EAA+B,CAC1D,OAAOC,EAAO,CACZ,MAAMP,EAASO,EAAQ,WACvB,MAAO,CACL,eAAeC,EAAI,CACjB,MAAMC,EAAWC,EAAaV,EAAQQ,CAAI,EAC1C,GAAIC,IAAa,OAAW,OAC5B,MAAMR,EAASO,EAAK,UAAU,CAAC,EAE/B,GAAI,CADaT,EAAeC,EAAQC,CAAM,GAC7BU,EAAaX,EAAQC,CAAM,IAAM,OAAW,OAC7D,MAAMW,EAAcC,EAAkBb,EAAQQ,EAAMC,EAAS,GAAIA,EAAS,OAAO,EACjFF,EAAQ,OAAO,CACb,KAAME,EAAS,GACf,UAAW,eACX,IAAKG,IAAgB,OAAY,KAAOE,GAASA,EAAM,YAAYL,EAAS,GAAIG,CAAW,CAC5F,CAAA,CACH,EAEJ,EACA,KAAM,CACJ,KAAM,CAAE,YAAa,qFAAqF,EAC1G,QAAS,OACT,SAAU,CACR,aACE,0NACH,EACD,OAAQ,CAAA,EACR,KAAM,YACP,EACD,KAAM,yBACP,CAAA"}
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as s}from"@typescript-eslint/utils";import{findProperty as b}from"../ast.js";import{isSlot as R,enclosingFunction as M,unwrap as T,localValue as c,isFunction as f,modelBinding as m,isApi as P}from"../model-bindings.js";import{createRule as U}from"../rule.js";function V(i){if(i.type===s.CallExpression)return i.arguments[0];if(i.type===s.ArrayExpression)return i.elements[0]??void 0}const W=U({create(i){const o=i.sourceCode,u=[],l=new Map,p=new Map,a=new Map,g=new Set,h=(e,n)=>P(o,e,"@opetope/react",n),w=e=>{if(e===void 0)return;const n=c(o,e);if(n.type!==s.ArrayExpression)return;const t=new Set;for(const r of n.elements){if(r===null)continue;const d=m(o,r);if(d===void 0)return;t.add(d)}return t},E=e=>{const n=T(e.callee);if(n.type!==s.CallExpression||!h(n.callee,"requiresModels"))return;const t=w(n.arguments[0]);p.set(e,t);const[r]=e.arguments;if(r===void 0)return;const d=c(o,r);f(d)&&a.set(d,t)},x=e=>{for(const n of e.elements){if(n===null)continue;const t=c(o,n),r=V(t),d=r===void 0?void 0:m(o,r);d!==void 0&&g.add(d)}},C=e=>p.has(e)||f(e)&&a.has(e),S=e=>{const n=c(o,e.value);C(n)||n.type===s.Identifier||n.type===s.MemberExpression||i.report({messageId:"missingWrapper",node:e})},q=e=>{const n=b(e,"Component"),t=b(e,"models");if(n===void 0||t===void 0)return;const r=c(o,t.value);r.type!==s.ArrayExpression||r.elements.length===0||(x(r),S(n))},A=e=>e.body===void 0?[]:e.body.type===s.BlockStatement?l.get(e)??[]:[e.body],y=(e,n=new Set)=>{const t=c(o,e);if(!n.has(t)){if(n.add(t),t.type===s.ObjectExpression)q(t);else if(f(t))for(const r of A(t))y(r,n)}},k=(e,n)=>{const t=M(o,e);if(t===void 0||!a.has(t))return!1;const r=a.get(t);return r===void 0||r.has(n)},I=e=>{if(!h(e.callee,"useModel"))return;const[n]=e.arguments,t=n===void 0?void 0:m(o,n);t===void 0||!g.has(t)||k(e,t)||i.report({data:{name:o.getText(n)},messageId:"undeclaredUiModel",node:e})};return{CallExpression(e){u.push(e)},ReturnStatement(e){const n=M(o,e);if(n===void 0||e.argument===null)return;const t=l.get(n)??[];t.push(e.argument),l.set(n,t)},"Program:exit"(){for(const e of u)E(e);for(const e of u){const n=e.arguments[1];n!==void 0&&R(o,e.callee)&&y(n)}for(const e of u)I(e)}}},meta:{docs:{description:"Each visible reader of a per-mount UI model declares it with requiresModels."},messages:{missingWrapper:"This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.",undeclaredUiModel:"A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns."},schema:[],type:"problem"},name:"require-declared-models"});export{W as requireDeclaredModels};
1
+ import{AST_NODE_TYPES as s}from"@typescript-eslint/utils";import{findProperty as b}from"../ast.js";import{bodyRecordEntries as T}from"../feature-body-records.js";import{isSlot as B,enclosingFunction as M,unwrap as P,localValue as c,isFunction as m,modelBinding as p,isApi as U}from"../model-bindings.js";import{createRule as V}from"../rule.js";function W(i){if(i.type===s.CallExpression)return i.arguments[0];if(i.type===s.ArrayExpression)return i.elements[0]??void 0}const D=V({create(i){const o=i.sourceCode,a=[],l=new Map,g=new Map,u=new Map,h=new Set,y=(e,n)=>U(o,e,"@opetope/react",n),E=e=>{if(e===void 0)return;const n=c(o,e);if(n.type!==s.ArrayExpression)return;const t=new Set;for(const r of n.elements){if(r===null)continue;const d=p(o,r);if(d===void 0)return;t.add(d)}return t},w=e=>{const n=P(e.callee);if(n.type!==s.CallExpression||!y(n.callee,"requiresModels"))return;const t=E(n.arguments[0]);g.set(e,t);const[r]=e.arguments;if(r===void 0)return;const d=c(o,r);m(d)&&u.set(d,t)},x=e=>{for(const n of e.elements){if(n===null)continue;const t=c(o,n),r=W(t),d=r===void 0?void 0:p(o,r);d!==void 0&&h.add(d)}},C=e=>g.has(e)||m(e)&&u.has(e),S=e=>{const n=c(o,e.value);C(n)||n.type===s.Identifier||n.type===s.MemberExpression||i.report({messageId:"missingWrapper",node:e})},q=e=>{const n=b(e,"Component"),t=b(e,"models");if(n===void 0||t===void 0)return;const r=c(o,t.value);r.type!==s.ArrayExpression||r.elements.length===0||(x(r),S(n))},k=e=>e.body===void 0?[]:e.body.type===s.BlockStatement?l.get(e)??[]:[e.body],f=(e,n=new Set)=>{const t=c(o,e);if(!n.has(t)){if(n.add(t),t.type===s.ObjectExpression)q(t);else if(m(t))for(const r of k(t))f(r,n)}},A=e=>{for(const n of T(o,e))n.implementation!==void 0&&f(n.implementation)},R=(e,n)=>{const t=M(o,e);if(t===void 0||!u.has(t))return!1;const r=u.get(t);return r===void 0||r.has(n)},I=e=>{if(!y(e.callee,"useModel"))return;const[n]=e.arguments,t=n===void 0?void 0:p(o,n);t===void 0||!h.has(t)||R(e,t)||i.report({data:{name:o.getText(n)},messageId:"undeclaredUiModel",node:e})};return{CallExpression(e){a.push(e)},ReturnStatement(e){const n=M(o,e);if(n===void 0||e.argument===null)return;const t=l.get(n)??[];t.push(e.argument),l.set(n,t)},"Program:exit"(){for(const e of a)w(e);for(const e of a){const n=e.arguments[1];n!==void 0&&B(o,e.callee)&&f(n),A(e)}for(const e of a)I(e)}}},meta:{docs:{description:"Each visible reader of a per-mount UI model declares it with requiresModels."},messages:{missingWrapper:"This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.",undeclaredUiModel:"A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns."},schema:[],type:"problem"},name:"require-declared-models"});export{D as requireDeclaredModels};
2
2
  //# sourceMappingURL=require-declared-models.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"require-declared-models.js","sources":["../../src/rules/require-declared-models.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty } from '../ast';\nimport type { FunctionNode, ModelBinding } from '../model-bindings';\nimport { enclosingFunction, isApi, isFunction, isSlot, localValue, modelBinding, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'missingWrapper' | 'undeclaredUiModel';\ntype Requirements = ReadonlySet<ModelBinding> | undefined;\n\nfunction plannedModel(value: TSESTree.Node): TSESTree.Node | undefined {\n if (value.type === AST_NODE_TYPES.CallExpression) return value.arguments[0];\n if (value.type === AST_NODE_TYPES.ArrayExpression) return value.elements[0] ?? undefined;\n return undefined;\n}\n\nconst requireDeclaredModels = createRule<readonly [], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const calls: TSESTree.CallExpression[] = [];\n const returns = new Map<FunctionNode, TSESTree.Node[]>();\n const wrappers = new Map<TSESTree.Node, Requirements>();\n const readers = new Map<FunctionNode, Requirements>();\n const granted = new Set<ModelBinding>();\n\n const api = (node: TSESTree.Node, name: string): boolean => isApi(source, node, '@opetope/react', name);\n\n const requirements = (node: TSESTree.Node | undefined): Requirements => {\n if (node === undefined) return undefined;\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n const bindings = new Set<ModelBinding>();\n\n for (const element of value.elements) {\n if (element === null) continue;\n\n const binding = modelBinding(source, element);\n\n if (binding === undefined) return undefined;\n\n bindings.add(binding);\n }\n\n return bindings;\n };\n\n const collectWrapper = (node: TSESTree.CallExpression): void => {\n const inner = unwrap(node.callee);\n\n if (inner.type !== AST_NODE_TYPES.CallExpression || !api(inner.callee, 'requiresModels')) return;\n\n const models = requirements(inner.arguments[0]);\n\n wrappers.set(node, models);\n const [argument] = node.arguments;\n\n if (argument === undefined) return;\n\n const component = localValue(source, argument);\n\n if (isFunction(component)) readers.set(component, models);\n };\n\n const grantModels = (node: TSESTree.ArrayExpression): void => {\n for (const element of node.elements) {\n if (element === null) continue;\n\n const value = localValue(source, element);\n const model = plannedModel(value);\n const binding = model === undefined ? undefined : modelBinding(source, model);\n\n if (binding !== undefined) granted.add(binding);\n }\n };\n\n const declaredComponent = (value: TSESTree.Node): boolean =>\n wrappers.has(value) || (isFunction(value) && readers.has(value));\n\n const checkComponent = (component: TSESTree.Property): void => {\n const value = localValue(source, component.value);\n if (declaredComponent(value)) return;\n // Imported components and parameters are unknown, not verified or rejected by this single-module rule.\n if (value.type === AST_NODE_TYPES.Identifier || value.type === AST_NODE_TYPES.MemberExpression) return;\n context.report({ messageId: 'missingWrapper', node: component });\n };\n\n const checkContribution = (object: TSESTree.ObjectExpression): void => {\n const component = findProperty(object, 'Component');\n const models = findProperty(object, 'models');\n\n if (component === undefined || models === undefined) return;\n\n const plans = localValue(source, models.value);\n\n if (plans.type !== AST_NODE_TYPES.ArrayExpression || plans.elements.length === 0) return;\n\n grantModels(plans);\n checkComponent(component);\n };\n\n const returnedValues = (value: FunctionNode): readonly TSESTree.Node[] => {\n if (value.body === undefined) return [];\n return value.body.type === AST_NODE_TYPES.BlockStatement ? (returns.get(value) ?? []) : [value.body];\n };\n\n const contributionValues = (node: TSESTree.Node, seen = new Set<TSESTree.Node>()): void => {\n const value = localValue(source, node);\n\n if (seen.has(value)) return;\n\n seen.add(value);\n if (value.type === AST_NODE_TYPES.ObjectExpression) checkContribution(value);\n else if (isFunction(value)) {\n for (const returned of returnedValues(value)) contributionValues(returned, seen);\n }\n };\n\n const declaresRead = (node: TSESTree.Node, binding: ModelBinding): boolean => {\n const fn = enclosingFunction(source, node);\n if (fn === undefined || !readers.has(fn)) return false;\n const declared = readers.get(fn);\n return declared === undefined || declared.has(binding);\n };\n\n const checkRead = (node: TSESTree.CallExpression): void => {\n if (!api(node.callee, 'useModel')) return;\n\n const [argument] = node.arguments;\n const binding = argument === undefined ? undefined : modelBinding(source, argument);\n\n if (binding === undefined || !granted.has(binding)) return;\n\n if (declaresRead(node, binding)) return;\n\n context.report({ data: { name: source.getText(argument) }, messageId: 'undeclaredUiModel', node });\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n ReturnStatement(node): void {\n const fn = enclosingFunction(source, node);\n\n if (fn === undefined || node.argument === null) return;\n\n const values = returns.get(fn) ?? [];\n\n values.push(node.argument);\n returns.set(fn, values);\n },\n 'Program:exit'(): void {\n for (const call of calls) collectWrapper(call);\n for (const call of calls) {\n const contribution = call.arguments[1];\n\n if (contribution !== undefined && isSlot(source, call.callee)) contributionValues(contribution);\n }\n for (const call of calls) checkRead(call);\n },\n };\n },\n meta: {\n docs: { description: 'Each visible reader of a per-mount UI model declares it with requiresModels.' },\n messages: {\n missingWrapper:\n 'This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.',\n undeclaredUiModel:\n 'A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'require-declared-models',\n});\n\nexport { requireDeclaredModels };\n"],"names":["plannedModel","value","AST_NODE_TYPES","requireDeclaredModels","createRule","context","source","calls","returns","wrappers","readers","granted","api","node","name","isApi","requirements","localValue","bindings","element","binding","modelBinding","collectWrapper","inner","unwrap","models","argument","component","isFunction","grantModels","model","declaredComponent","checkComponent","checkContribution","object","findProperty","plans","returnedValues","contributionValues","seen","returned","declaresRead","fn","enclosingFunction","declared","checkRead","values","call","contribution","isSlot"],"mappings":"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"}
1
+ {"version":3,"file":"require-declared-models.js","sources":["../../src/rules/require-declared-models.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty } from '../ast';\nimport { bodyRecordEntries } from '../feature-body-records';\nimport type { FunctionNode, ModelBinding } from '../model-bindings';\nimport { enclosingFunction, isApi, isFunction, isSlot, localValue, modelBinding, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'missingWrapper' | 'undeclaredUiModel';\ntype Requirements = ReadonlySet<ModelBinding> | undefined;\n\nfunction plannedModel(value: TSESTree.Node): TSESTree.Node | undefined {\n if (value.type === AST_NODE_TYPES.CallExpression) return value.arguments[0];\n if (value.type === AST_NODE_TYPES.ArrayExpression) return value.elements[0] ?? undefined;\n return undefined;\n}\n\nconst requireDeclaredModels = createRule<readonly [], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const calls: TSESTree.CallExpression[] = [];\n const returns = new Map<FunctionNode, TSESTree.Node[]>();\n const wrappers = new Map<TSESTree.Node, Requirements>();\n const readers = new Map<FunctionNode, Requirements>();\n const granted = new Set<ModelBinding>();\n\n const api = (node: TSESTree.Node, name: string): boolean => isApi(source, node, '@opetope/react', name);\n\n const requirements = (node: TSESTree.Node | undefined): Requirements => {\n if (node === undefined) return undefined;\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n const bindings = new Set<ModelBinding>();\n\n for (const element of value.elements) {\n if (element === null) continue;\n\n const binding = modelBinding(source, element);\n\n if (binding === undefined) return undefined;\n\n bindings.add(binding);\n }\n\n return bindings;\n };\n\n const collectWrapper = (node: TSESTree.CallExpression): void => {\n const inner = unwrap(node.callee);\n\n if (inner.type !== AST_NODE_TYPES.CallExpression || !api(inner.callee, 'requiresModels')) return;\n\n const models = requirements(inner.arguments[0]);\n\n wrappers.set(node, models);\n const [argument] = node.arguments;\n\n if (argument === undefined) return;\n\n const component = localValue(source, argument);\n\n if (isFunction(component)) readers.set(component, models);\n };\n\n const grantModels = (node: TSESTree.ArrayExpression): void => {\n for (const element of node.elements) {\n if (element === null) continue;\n\n const value = localValue(source, element);\n const model = plannedModel(value);\n const binding = model === undefined ? undefined : modelBinding(source, model);\n\n if (binding !== undefined) granted.add(binding);\n }\n };\n\n const declaredComponent = (value: TSESTree.Node): boolean =>\n wrappers.has(value) || (isFunction(value) && readers.has(value));\n\n const checkComponent = (component: TSESTree.Property): void => {\n const value = localValue(source, component.value);\n if (declaredComponent(value)) return;\n // Imported components and parameters are unknown, not verified or rejected by this single-module rule.\n if (value.type === AST_NODE_TYPES.Identifier || value.type === AST_NODE_TYPES.MemberExpression) return;\n context.report({ messageId: 'missingWrapper', node: component });\n };\n\n const checkContribution = (object: TSESTree.ObjectExpression): void => {\n const component = findProperty(object, 'Component');\n const models = findProperty(object, 'models');\n\n if (component === undefined || models === undefined) return;\n\n const plans = localValue(source, models.value);\n\n if (plans.type !== AST_NODE_TYPES.ArrayExpression || plans.elements.length === 0) return;\n\n grantModels(plans);\n checkComponent(component);\n };\n\n const returnedValues = (value: FunctionNode): readonly TSESTree.Node[] => {\n if (value.body === undefined) return [];\n return value.body.type === AST_NODE_TYPES.BlockStatement ? (returns.get(value) ?? []) : [value.body];\n };\n\n const contributionValues = (node: TSESTree.Node, seen = new Set<TSESTree.Node>()): void => {\n const value = localValue(source, node);\n\n if (seen.has(value)) return;\n\n seen.add(value);\n if (value.type === AST_NODE_TYPES.ObjectExpression) checkContribution(value);\n else if (isFunction(value)) {\n for (const returned of returnedValues(value)) contributionValues(returned, seen);\n }\n };\n\n const checkBodyRecord = (call: TSESTree.CallExpression): void => {\n for (const entry of bodyRecordEntries(source, call)) {\n if (entry.implementation !== undefined) contributionValues(entry.implementation);\n }\n };\n\n const declaresRead = (node: TSESTree.Node, binding: ModelBinding): boolean => {\n const fn = enclosingFunction(source, node);\n if (fn === undefined || !readers.has(fn)) return false;\n const declared = readers.get(fn);\n return declared === undefined || declared.has(binding);\n };\n\n const checkRead = (node: TSESTree.CallExpression): void => {\n if (!api(node.callee, 'useModel')) return;\n\n const [argument] = node.arguments;\n const binding = argument === undefined ? undefined : modelBinding(source, argument);\n\n if (binding === undefined || !granted.has(binding)) return;\n\n if (declaresRead(node, binding)) return;\n\n context.report({ data: { name: source.getText(argument) }, messageId: 'undeclaredUiModel', node });\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n ReturnStatement(node): void {\n const fn = enclosingFunction(source, node);\n\n if (fn === undefined || node.argument === null) return;\n\n const values = returns.get(fn) ?? [];\n\n values.push(node.argument);\n returns.set(fn, values);\n },\n 'Program:exit'(): void {\n for (const call of calls) collectWrapper(call);\n for (const call of calls) {\n const contribution = call.arguments[1];\n\n if (contribution !== undefined && isSlot(source, call.callee)) contributionValues(contribution);\n checkBodyRecord(call);\n }\n for (const call of calls) checkRead(call);\n },\n };\n },\n meta: {\n docs: { description: 'Each visible reader of a per-mount UI model declares it with requiresModels.' },\n messages: {\n missingWrapper:\n 'This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.',\n undeclaredUiModel:\n 'A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'require-declared-models',\n});\n\nexport { requireDeclaredModels };\n"],"names":["plannedModel","value","AST_NODE_TYPES","requireDeclaredModels","createRule","context","source","calls","returns","wrappers","readers","granted","api","node","name","isApi","requirements","localValue","bindings","element","binding","modelBinding","collectWrapper","inner","unwrap","models","argument","component","isFunction","grantModels","model","declaredComponent","checkComponent","checkContribution","object","findProperty","plans","returnedValues","contributionValues","seen","returned","checkBodyRecord","call","entry","bodyRecordEntries","declaresRead","fn","enclosingFunction","declared","checkRead","values","contribution","isSlot"],"mappings":"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"}
package/oxlintrc.json CHANGED
@@ -13,6 +13,7 @@
13
13
  "opetope/no-subscribe-outside-models": "off",
14
14
  "opetope/no-write-after-source-write": "error",
15
15
  "opetope/prefer-effect-current": "error",
16
+ "opetope/prefer-lazy-body-record": "warn",
16
17
  "opetope/prefer-model-selection": "off",
17
18
  "opetope/require-declared-models": "error",
18
19
  "opetope/require-literal-id": "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/lint",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },