@opetope/lint 0.12.2 → 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,15 @@
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
+
3
13
  ## 0.12.2
4
14
 
5
15
  ### 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
@@ -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.2"},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.2' },\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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"}
@@ -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":"oWAWA,MAAMA,EAAwB,IAAI,IAAI,CAAC,OAAQ,WAAY,MAAM,CAAC,EAC5DC,EAAe,IAAI,IAAI,CAAC,UAAW,UAAW,MAAO,MAAM,CAAC,EAC5DC,EAAkB,IAAI,IAAI,CAAC,UAAW,UAAW,KAAK,CAAC,EAG7D,SAASC,EAAiBC,EAA+B,CACvD,KAAM,CAAE,OAAAC,CAAM,EAAKD,EAEnB,GAAIC,EAAO,OAASC,EAAe,gBAAkBD,EAAO,UAAU,OAAS,EAAG,MAAO,GAEzF,MAAME,EAAOC,EAAWH,CAAM,EAE9B,OAAOE,IAAS,QAAaP,EAAsB,IAAIO,CAAI,GAAKF,EAAO,UAAU,GAAG,EAAE,IAAMD,CAC9F,CAGA,SAASK,EAAeC,EAAgB,CACtC,KAAM,CAACC,CAAS,EAAID,EAAG,OAEvB,GAAI,EAAAA,EAAG,OAAO,SAAW,GAAKC,GAAW,OAASL,EAAe,eAEjE,MAAO,CAAC,GAAGM,EAAYD,CAAS,CAAC,EAAE,KAAKE,GAAOZ,EAAa,IAAIY,CAAG,CAAC,EAAIF,EAAY,MACtF,CAGA,SAASG,EAAkBJ,EAAgB,CACzC,KAAM,CAACC,CAAS,EAAID,EAAG,OAEvB,GAAI,EAAAA,EAAG,OAAO,SAAW,GAAKC,GAAW,OAASL,EAAe,YAEjE,OAAOK,CACT,CAEA,SAASI,EAAqBJ,EAAgCK,EAA+B,CAC3F,MAAMC,EAAOC,EAAWF,CAAI,EACtBG,EAAOC,EAAgBJ,CAAI,EAEjC,OAAIC,EAAK,OAASX,EAAe,YAAcW,EAAK,OAASN,EAAU,KAAa,GAE7EQ,IAAS,QAAajB,EAAgB,IAAIiB,CAAI,CACvD,CAEA,SAASE,EAA4BC,EAAiCN,EAA+B,CACnG,MAAMC,EAAOC,EAAWF,CAAI,EAE5B,OAAIC,EAAK,OAASX,EAAe,WAAmB,GAE7CJ,EAAgB,IAAIe,EAAK,IAAI,GAAKL,EAAYU,CAAO,EAAE,IAAIL,EAAK,IAAI,CAC7E,CAGA,SAASM,EAAUb,EAAkBM,EAA+B,CAClE,MAAMM,EAAUb,EAAeC,CAAE,EAEjC,GAAIY,IAAY,OAAW,OAAOD,EAA4BC,EAASN,CAAI,EAAI,OAAS,OAExF,MAAML,EAAYG,EAAkBJ,CAAE,EAEtC,GAAIC,IAAc,OAElB,OAAOI,EAAqBJ,EAAWK,CAAI,EAAI,GAAGL,EAAU,IAAI,QAAU,MAC5E,CAGA,SAASa,EAAcC,EAA2BH,EAA+B,CAC/E,MAAMI,EAAYJ,EAAQ,WAAW,KAAKK,GAAW,CACnD,MAAMpB,EAAOqB,EAAaD,CAAQ,EAElC,OAAOpB,IAAS,QAAaA,EAAO,MACtC,CAAC,EAED,GAAImB,IAAc,OAAW,OAAOD,EAAM,iBAAiBC,EAAW,QAAQ,EAE9E,MAAMG,EAAOP,EAAQ,WAAW,GAAG,EAAE,EAErC,OAAOO,IAAS,OAAY,OAAYJ,EAAM,gBAAgBI,EAAM,QAAQ,CAC9E,CAEA,SAASC,EAAepB,EAAkBqB,EAA+B,CACvE,OAAOrB,EAAG,OAAO,KACfC,GAAaA,EAAU,OAASL,EAAe,YAAcK,EAAU,OAASoB,EAAW,IAAI,CAEnG,CAEA,SAASC,EAAsBL,EAA6BjB,EAAgB,CAC1E,KAAM,CAAE,OAAAL,CAAM,EAAKsB,EAEnB,OACGtB,EAAO,OAASC,EAAe,kBAAoBH,EAAiBE,CAAM,GAAMI,EAAeC,CAAE,IAAM,MAE5G,CAEA,MAAMuB,EAAmBC,EAA2B,CAClD,OAAOC,EAAO,CACZ,MAAMC,EAAmC,CAAA,EACnCC,EAAU,IAAI,IACdC,EAAe,CAAC5B,EAAkBM,IAAyC,CAC/E,MAAMuB,EAAShB,EAAUb,EAAIM,CAAI,EAE7BuB,IAAW,QAEfJ,EAAQ,OAAO,CACb,IAAIV,EAAK,CACP,MAAMH,EAAUb,EAAeC,CAAE,EAC3B8B,EAAUf,EAAM,YAAYT,EAAM,GAAGuB,CAAM,IAAIJ,EAAQ,WAAW,QAAQnB,CAAI,CAAC,GAAG,EAExF,GAAIM,IAAY,QAAaV,EAAYU,CAAO,EAAE,IAAI,MAAM,EAAG,MAAO,CAACkB,CAAO,EAE9E,MAAMC,EAAWjB,EAAcC,EAAOH,CAAO,EAE7C,OAAOmB,IAAa,OAAY,CAACD,CAAO,EAAI,CAACA,EAASC,CAAQ,CAChE,EACA,UAAW,uBACX,KAAMzB,CACP,CAAA,CACH,EAEM0B,EAAShC,GAA0B,CACvC,GAAI2B,EAAQ,IAAI3B,CAAE,EAAG,OAErB,GADA2B,EAAQ,IAAI3B,CAAE,EACVA,EAAG,MAAO,CACZyB,EAAQ,OAAO,CAAE,UAAW,iBAAkB,KAAMzB,EAAI,EAExD,MACF,CAEA,GAAIA,EAAG,OAASJ,EAAe,wBAAyB,OAExD,KAAM,CAAE,KAAAU,CAAI,EAAKN,EAEjB,GAAIM,EAAK,OAASV,EAAe,iBAAkB,CACjDgC,EAAa5B,EAAIM,CAAI,EAErB,MACF,CAEIA,EAAK,OAASV,EAAe,YAAc,CAACwB,EAAepB,EAAIM,CAAI,GACrEmB,EAAQ,OAAO,CAAE,UAAW,2BAA4B,KAAMnB,EAAM,CAExE,EAEA,MAAO,CACL,eAAeZ,EAAI,CACjBgC,EAAM,KAAKhC,CAAI,CACjB,EACA,gBAAc,CACZ,UAAWuC,KAAQP,EACjB,UAAWQ,KAASC,EAAkBV,EAAQ,WAAYQ,CAAI,EAAG,CAC/D,GAAIC,EAAM,UAAY,OAAW,SACjC,MAAME,EAAUC,EAAWZ,EAAQ,WAAYS,EAAM,OAAO,EAC5D,GAAIE,EAAQ,OAASxC,EAAe,iBAAkB,SACtD,MAAM0C,EAAUC,EAAaH,EAAS,SAAS,EAC/C,GAAIE,IAAY,OAAW,SAC3B,MAAME,EAAYH,EAAWZ,EAAQ,WAAYa,EAAQ,KAAK,EAC1DG,EAAWD,CAAS,GAAGR,EAAMQ,CAAS,CAC5C,CAEJ,EACA,SAAS9C,EAAI,CACPwB,EAAaxB,CAAI,IAAM,WAAa,CAAC+C,EAAW/C,EAAK,KAAK,GAE1D4B,EAAsB5B,EAAMA,EAAK,KAAK,GAAGsC,EAAMtC,EAAK,KAAK,CAC/D,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,iGACd,EACD,QAAS,OACT,SAAU,CACR,eACE,oHACF,yBACE,2HACF,qBACE,6GACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,mBACP,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":"yRAWA,SAASA,EAAaC,EAAoB,CACxC,GAAIA,EAAM,OAASC,EAAe,eAAgB,OAAOD,EAAM,UAAU,CAAC,EAC1E,GAAIA,EAAM,OAASC,EAAe,gBAAiB,OAAOD,EAAM,SAAS,CAAC,GAAK,MAEjF,CAEA,MAAME,EAAwBC,EAAoC,CAChE,OAAOC,EAAO,CACZ,MAAMC,EAASD,EAAQ,WACjBE,EAAmC,CAAA,EACnCC,EAAU,IAAI,IACdC,EAAW,IAAI,IACfC,EAAU,IAAI,IACdC,EAAU,IAAI,IAEdC,EAAM,CAACC,EAAqBC,IAA0BC,EAAMT,EAAQO,EAAM,iBAAkBC,CAAI,EAEhGE,EAAgBH,GAAiD,CACrE,GAAIA,IAAS,OAAW,OAExB,MAAMZ,EAAQgB,EAAWX,EAAQO,CAAI,EAErC,GAAIZ,EAAM,OAASC,EAAe,gBAAiB,OAEnD,MAAMgB,EAAW,IAAI,IAErB,UAAWC,KAAWlB,EAAM,SAAU,CACpC,GAAIkB,IAAY,KAAM,SAEtB,MAAMC,EAAUC,EAAaf,EAAQa,CAAO,EAE5C,GAAIC,IAAY,OAAW,OAE3BF,EAAS,IAAIE,CAAO,CACtB,CAEA,OAAOF,CACT,EAEMI,EAAkBT,GAAuC,CAC7D,MAAMU,EAAQC,EAAOX,EAAK,MAAM,EAEhC,GAAIU,EAAM,OAASrB,EAAe,gBAAkB,CAACU,EAAIW,EAAM,OAAQ,gBAAgB,EAAG,OAE1F,MAAME,EAAST,EAAaO,EAAM,UAAU,CAAC,CAAC,EAE9Cd,EAAS,IAAII,EAAMY,CAAM,EACzB,KAAM,CAACC,CAAQ,EAAIb,EAAK,UAExB,GAAIa,IAAa,OAAW,OAE5B,MAAMC,EAAYV,EAAWX,EAAQoB,CAAQ,EAEzCE,EAAWD,CAAS,GAAGjB,EAAQ,IAAIiB,EAAWF,CAAM,CAC1D,EAEMI,EAAehB,GAAwC,CAC3D,UAAWM,KAAWN,EAAK,SAAU,CACnC,GAAIM,IAAY,KAAM,SAEtB,MAAMlB,EAAQgB,EAAWX,EAAQa,CAAO,EAClCW,EAAQ9B,EAAaC,CAAK,EAC1BmB,EAAUU,IAAU,OAAY,OAAYT,EAAaf,EAAQwB,CAAK,EAExEV,IAAY,QAAWT,EAAQ,IAAIS,CAAO,CAChD,CACF,EAEMW,EAAqB9B,GACzBQ,EAAS,IAAIR,CAAK,GAAM2B,EAAW3B,CAAK,GAAKS,EAAQ,IAAIT,CAAK,EAE1D+B,EAAkBL,GAAsC,CAC5D,MAAM1B,EAAQgB,EAAWX,EAAQqB,EAAU,KAAK,EAC5CI,EAAkB9B,CAAK,GAEvBA,EAAM,OAASC,EAAe,YAAcD,EAAM,OAASC,EAAe,kBAC9EG,EAAQ,OAAO,CAAE,UAAW,iBAAkB,KAAMsB,EAAW,CACjE,EAEMM,EAAqBC,GAA2C,CACpE,MAAMP,EAAYQ,EAAaD,EAAQ,WAAW,EAC5CT,EAASU,EAAaD,EAAQ,QAAQ,EAE5C,GAAIP,IAAc,QAAaF,IAAW,OAAW,OAErD,MAAMW,EAAQnB,EAAWX,EAAQmB,EAAO,KAAK,EAEzCW,EAAM,OAASlC,EAAe,iBAAmBkC,EAAM,SAAS,SAAW,IAE/EP,EAAYO,CAAK,EACjBJ,EAAeL,CAAS,EAC1B,EAEMU,EAAkBpC,GAClBA,EAAM,OAAS,OAAkB,CAAA,EAC9BA,EAAM,KAAK,OAASC,EAAe,eAAkBM,EAAQ,IAAIP,CAAK,GAAK,CAAA,EAAM,CAACA,EAAM,IAAI,EAG/FqC,EAAqB,CAACzB,EAAqB0B,EAAO,IAAI,MAA8B,CACxF,MAAMtC,EAAQgB,EAAWX,EAAQO,CAAI,EAErC,GAAI,CAAA0B,EAAK,IAAItC,CAAK,GAGlB,GADAsC,EAAK,IAAItC,CAAK,EACVA,EAAM,OAASC,EAAe,iBAAkB+B,EAAkBhC,CAAK,UAClE2B,EAAW3B,CAAK,EACvB,UAAWuC,KAAYH,EAAepC,CAAK,EAAGqC,EAAmBE,EAAUD,CAAI,EAEnF,EAEME,EAAe,CAAC5B,EAAqBO,IAAkC,CAC3E,MAAMsB,EAAKC,EAAkBrC,EAAQO,CAAI,EACzC,GAAI6B,IAAO,QAAa,CAAChC,EAAQ,IAAIgC,CAAE,EAAG,MAAO,GACjD,MAAME,EAAWlC,EAAQ,IAAIgC,CAAE,EAC/B,OAAOE,IAAa,QAAaA,EAAS,IAAIxB,CAAO,CACvD,EAEMyB,EAAahC,GAAuC,CACxD,GAAI,CAACD,EAAIC,EAAK,OAAQ,UAAU,EAAG,OAEnC,KAAM,CAACa,CAAQ,EAAIb,EAAK,UAClBO,EAAUM,IAAa,OAAY,OAAYL,EAAaf,EAAQoB,CAAQ,EAE9EN,IAAY,QAAa,CAACT,EAAQ,IAAIS,CAAO,GAE7CqB,EAAa5B,EAAMO,CAAO,GAE9Bf,EAAQ,OAAO,CAAE,KAAM,CAAE,KAAMC,EAAO,QAAQoB,CAAQ,CAAC,EAAI,UAAW,oBAAqB,KAAAb,CAAI,CAAE,CACnG,EAEA,MAAO,CACL,eAAeA,EAAI,CACjBN,EAAM,KAAKM,CAAI,CACjB,EACA,gBAAgBA,EAAI,CAClB,MAAM6B,EAAKC,EAAkBrC,EAAQO,CAAI,EAEzC,GAAI6B,IAAO,QAAa7B,EAAK,WAAa,KAAM,OAEhD,MAAMiC,EAAStC,EAAQ,IAAIkC,CAAE,GAAK,CAAA,EAElCI,EAAO,KAAKjC,EAAK,QAAQ,EACzBL,EAAQ,IAAIkC,EAAII,CAAM,CACxB,EACA,gBAAc,CACZ,UAAWC,KAAQxC,EAAOe,EAAeyB,CAAI,EAC7C,UAAWA,KAAQxC,EAAO,CACxB,MAAMyC,EAAeD,EAAK,UAAU,CAAC,EAEjCC,IAAiB,QAAaC,EAAO3C,EAAQyC,EAAK,MAAM,GAAGT,EAAmBU,CAAY,CAChG,CACA,UAAWD,KAAQxC,EAAOsC,EAAUE,CAAI,CAC1C,EAEJ,EACA,KAAM,CACJ,KAAM,CAAE,YAAa,8EAA8E,EACnG,SAAU,CACR,eACE,4GACF,kBACE,wJACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,yBACP,CAAA"}
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":"wVAYA,SAASA,EAAaC,EAAoB,CACxC,GAAIA,EAAM,OAASC,EAAe,eAAgB,OAAOD,EAAM,UAAU,CAAC,EAC1E,GAAIA,EAAM,OAASC,EAAe,gBAAiB,OAAOD,EAAM,SAAS,CAAC,GAAK,MAEjF,CAEA,MAAME,EAAwBC,EAAoC,CAChE,OAAOC,EAAO,CACZ,MAAMC,EAASD,EAAQ,WACjBE,EAAmC,CAAA,EACnCC,EAAU,IAAI,IACdC,EAAW,IAAI,IACfC,EAAU,IAAI,IACdC,EAAU,IAAI,IAEdC,EAAM,CAACC,EAAqBC,IAA0BC,EAAMT,EAAQO,EAAM,iBAAkBC,CAAI,EAEhGE,EAAgBH,GAAiD,CACrE,GAAIA,IAAS,OAAW,OAExB,MAAMZ,EAAQgB,EAAWX,EAAQO,CAAI,EAErC,GAAIZ,EAAM,OAASC,EAAe,gBAAiB,OAEnD,MAAMgB,EAAW,IAAI,IAErB,UAAWC,KAAWlB,EAAM,SAAU,CACpC,GAAIkB,IAAY,KAAM,SAEtB,MAAMC,EAAUC,EAAaf,EAAQa,CAAO,EAE5C,GAAIC,IAAY,OAAW,OAE3BF,EAAS,IAAIE,CAAO,CACtB,CAEA,OAAOF,CACT,EAEMI,EAAkBT,GAAuC,CAC7D,MAAMU,EAAQC,EAAOX,EAAK,MAAM,EAEhC,GAAIU,EAAM,OAASrB,EAAe,gBAAkB,CAACU,EAAIW,EAAM,OAAQ,gBAAgB,EAAG,OAE1F,MAAME,EAAST,EAAaO,EAAM,UAAU,CAAC,CAAC,EAE9Cd,EAAS,IAAII,EAAMY,CAAM,EACzB,KAAM,CAACC,CAAQ,EAAIb,EAAK,UAExB,GAAIa,IAAa,OAAW,OAE5B,MAAMC,EAAYV,EAAWX,EAAQoB,CAAQ,EAEzCE,EAAWD,CAAS,GAAGjB,EAAQ,IAAIiB,EAAWF,CAAM,CAC1D,EAEMI,EAAehB,GAAwC,CAC3D,UAAWM,KAAWN,EAAK,SAAU,CACnC,GAAIM,IAAY,KAAM,SAEtB,MAAMlB,EAAQgB,EAAWX,EAAQa,CAAO,EAClCW,EAAQ9B,EAAaC,CAAK,EAC1BmB,EAAUU,IAAU,OAAY,OAAYT,EAAaf,EAAQwB,CAAK,EAExEV,IAAY,QAAWT,EAAQ,IAAIS,CAAO,CAChD,CACF,EAEMW,EAAqB9B,GACzBQ,EAAS,IAAIR,CAAK,GAAM2B,EAAW3B,CAAK,GAAKS,EAAQ,IAAIT,CAAK,EAE1D+B,EAAkBL,GAAsC,CAC5D,MAAM1B,EAAQgB,EAAWX,EAAQqB,EAAU,KAAK,EAC5CI,EAAkB9B,CAAK,GAEvBA,EAAM,OAASC,EAAe,YAAcD,EAAM,OAASC,EAAe,kBAC9EG,EAAQ,OAAO,CAAE,UAAW,iBAAkB,KAAMsB,EAAW,CACjE,EAEMM,EAAqBC,GAA2C,CACpE,MAAMP,EAAYQ,EAAaD,EAAQ,WAAW,EAC5CT,EAASU,EAAaD,EAAQ,QAAQ,EAE5C,GAAIP,IAAc,QAAaF,IAAW,OAAW,OAErD,MAAMW,EAAQnB,EAAWX,EAAQmB,EAAO,KAAK,EAEzCW,EAAM,OAASlC,EAAe,iBAAmBkC,EAAM,SAAS,SAAW,IAE/EP,EAAYO,CAAK,EACjBJ,EAAeL,CAAS,EAC1B,EAEMU,EAAkBpC,GAClBA,EAAM,OAAS,OAAkB,CAAA,EAC9BA,EAAM,KAAK,OAASC,EAAe,eAAkBM,EAAQ,IAAIP,CAAK,GAAK,CAAA,EAAM,CAACA,EAAM,IAAI,EAG/FqC,EAAqB,CAACzB,EAAqB0B,EAAO,IAAI,MAA8B,CACxF,MAAMtC,EAAQgB,EAAWX,EAAQO,CAAI,EAErC,GAAI,CAAA0B,EAAK,IAAItC,CAAK,GAGlB,GADAsC,EAAK,IAAItC,CAAK,EACVA,EAAM,OAASC,EAAe,iBAAkB+B,EAAkBhC,CAAK,UAClE2B,EAAW3B,CAAK,EACvB,UAAWuC,KAAYH,EAAepC,CAAK,EAAGqC,EAAmBE,EAAUD,CAAI,EAEnF,EAEME,EAAmBC,GAAuC,CAC9D,UAAWC,KAASC,EAAkBtC,EAAQoC,CAAI,EAC5CC,EAAM,iBAAmB,QAAWL,EAAmBK,EAAM,cAAc,CAEnF,EAEME,EAAe,CAAChC,EAAqBO,IAAkC,CAC3E,MAAM0B,EAAKC,EAAkBzC,EAAQO,CAAI,EACzC,GAAIiC,IAAO,QAAa,CAACpC,EAAQ,IAAIoC,CAAE,EAAG,MAAO,GACjD,MAAME,EAAWtC,EAAQ,IAAIoC,CAAE,EAC/B,OAAOE,IAAa,QAAaA,EAAS,IAAI5B,CAAO,CACvD,EAEM6B,EAAapC,GAAuC,CACxD,GAAI,CAACD,EAAIC,EAAK,OAAQ,UAAU,EAAG,OAEnC,KAAM,CAACa,CAAQ,EAAIb,EAAK,UAClBO,EAAUM,IAAa,OAAY,OAAYL,EAAaf,EAAQoB,CAAQ,EAE9EN,IAAY,QAAa,CAACT,EAAQ,IAAIS,CAAO,GAE7CyB,EAAahC,EAAMO,CAAO,GAE9Bf,EAAQ,OAAO,CAAE,KAAM,CAAE,KAAMC,EAAO,QAAQoB,CAAQ,CAAC,EAAI,UAAW,oBAAqB,KAAAb,CAAI,CAAE,CACnG,EAEA,MAAO,CACL,eAAeA,EAAI,CACjBN,EAAM,KAAKM,CAAI,CACjB,EACA,gBAAgBA,EAAI,CAClB,MAAMiC,EAAKC,EAAkBzC,EAAQO,CAAI,EAEzC,GAAIiC,IAAO,QAAajC,EAAK,WAAa,KAAM,OAEhD,MAAMqC,EAAS1C,EAAQ,IAAIsC,CAAE,GAAK,CAAA,EAElCI,EAAO,KAAKrC,EAAK,QAAQ,EACzBL,EAAQ,IAAIsC,EAAII,CAAM,CACxB,EACA,gBAAc,CACZ,UAAWR,KAAQnC,EAAOe,EAAeoB,CAAI,EAC7C,UAAWA,KAAQnC,EAAO,CACxB,MAAM4C,EAAeT,EAAK,UAAU,CAAC,EAEjCS,IAAiB,QAAaC,EAAO9C,EAAQoC,EAAK,MAAM,GAAGJ,EAAmBa,CAAY,EAC9FV,EAAgBC,CAAI,CACtB,CACA,UAAWA,KAAQnC,EAAO0C,EAAUP,CAAI,CAC1C,EAEJ,EACA,KAAM,CACJ,KAAM,CAAE,YAAa,8EAA8E,EACnG,SAAU,CACR,eACE,4GACF,kBACE,wJACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,yBACP,CAAA"}
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.2",
3
+ "version": "0.12.3",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },