@opetope/lint 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +335 -0
  4. package/README.ru.md +335 -0
  5. package/dist/ast.d.ts +18 -0
  6. package/dist/ast.js +2 -0
  7. package/dist/ast.js.map +1 -0
  8. package/dist/declarations.d.ts +8 -0
  9. package/dist/declarations.js +2 -0
  10. package/dist/declarations.js.map +1 -0
  11. package/dist/index.d.ts +103 -0
  12. package/dist/index.js +2 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/model-bindings.d.ts +14 -0
  15. package/dist/model-bindings.js +2 -0
  16. package/dist/model-bindings.js.map +1 -0
  17. package/dist/rule.d.ts +9 -0
  18. package/dist/rule.js +2 -0
  19. package/dist/rule.js.map +1 -0
  20. package/dist/rules/define-feature-property-order.d.ts +5 -0
  21. package/dist/rules/define-feature-property-order.js +2 -0
  22. package/dist/rules/define-feature-property-order.js.map +1 -0
  23. package/dist/rules/id-naming.d.ts +8 -0
  24. package/dist/rules/id-naming.js +2 -0
  25. package/dist/rules/id-naming.js.map +1 -0
  26. package/dist/rules/layer-placement.d.ts +8 -0
  27. package/dist/rules/layer-placement.js +2 -0
  28. package/dist/rules/layer-placement.js.map +1 -0
  29. package/dist/rules/no-snapshot-read-in-render.d.ts +4 -0
  30. package/dist/rules/no-snapshot-read-in-render.js +2 -0
  31. package/dist/rules/no-snapshot-read-in-render.js.map +1 -0
  32. package/dist/rules/no-subscribe-outside-models.d.ts +4 -0
  33. package/dist/rules/no-subscribe-outside-models.js +2 -0
  34. package/dist/rules/no-subscribe-outside-models.js.map +1 -0
  35. package/dist/rules/prefer-model-selection.d.ts +7 -0
  36. package/dist/rules/prefer-model-selection.js +2 -0
  37. package/dist/rules/prefer-model-selection.js.map +1 -0
  38. package/dist/rules/require-declared-models.d.ts +5 -0
  39. package/dist/rules/require-declared-models.js +2 -0
  40. package/dist/rules/require-declared-models.js.map +1 -0
  41. package/dist/rules/require-literal-id.d.ts +7 -0
  42. package/dist/rules/require-literal-id.js +2 -0
  43. package/dist/rules/require-literal-id.js.map +1 -0
  44. package/dist/rules/when-predicate.d.ts +6 -0
  45. package/dist/rules/when-predicate.js +2 -0
  46. package/dist/rules/when-predicate.js.map +1 -0
  47. package/dist/static-value.d.ts +15 -0
  48. package/dist/static-value.js +2 -0
  49. package/dist/static-value.js.map +1 -0
  50. package/package.json +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule.js","sources":["../src/rule.ts"],"sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\n/**\n * Every rule documents itself in the package README; the anchor is the rule name, so a report always names the\n * section that explains the law behind it.\n */\nconst createRule = ESLintUtils.RuleCreator(name => `@opetope/lint/README.md#${name}`);\n\nexport { createRule };\n"],"names":["createRule","ESLintUtils","name"],"mappings":"uDAMA,MAAMA,EAAaC,EAAY,YAAYC,GAAQ,2BAA2BA,CAAI,EAAE"}
@@ -0,0 +1,5 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ declare const defineFeaturePropertyOrder: TSESLint.RuleModule<"sectionOrder", [], unknown, TSESLint.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export { defineFeaturePropertyOrder };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as p}from"@typescript-eslint/utils";import{calleeName as f,propertyName as a}from"../ast.js";import{createRule as u}from"../rule.js";const c=["id","imports","requires","own","exports","provides","when","body"];function d(o){const n=a(o);return n===void 0?-1:c.indexOf(n)}function m(o){const n=[];for(const e of o){const r=d(e);if(r<0)return;n.push(r)}return n}function l(o){let n=-1;for(const[e,r]of o.entries()){if(r<n)return e;n=r}return-1}function x(o,n){return o.getCommentsInside(n).some(e=>!n.properties.some(r=>r.range[0]<=e.range[0]&&e.range[1]<=r.range[1]))}function*g(o,n,e){const r=[...e].sort((s,t)=>d(s)-d(t));for(const[s,t]of e.entries()){const i=r[s];i!==void 0&&i!==t&&(yield o.replaceText(t,n.getText(i)))}}const y=u({create(o){const n=e=>{const r=m(e.properties);if(r===void 0)return;const s=l(r),t=e.properties[s];t!==void 0&&o.report({fix:x(o.sourceCode,e)?null:i=>[...g(i,o.sourceCode,e.properties)],messageId:"sectionOrder",node:t})};return{CallExpression(e){const[r]=e.arguments;f(e)!=="defineFeature"||r?.type!==p.ObjectExpression||n(r)}}},meta:{docs:{description:"The sections of `defineFeature` keep the declaration order of the specification."},fixable:"code",messages:{sectionOrder:`A feature declares its sections in one order: ${c.join(", ")} (spec \xA72.1, D186).`},schema:[],type:"layout"},name:"define-feature-property-order"});export{y as defineFeaturePropertyOrder};
2
+ //# sourceMappingURL=define-feature-property-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-feature-property-order.js","sources":["../../src/rules/define-feature-property-order.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'sectionOrder';\ntype Properties = readonly TSESTree.ObjectLiteralElement[];\n\n/**\n * The declaration order of the sections: identity, the edges, the stages, then the lifetime and the loader that\n * replaces the last three stages. The specification names the sections; the order is the one a key sorter can also\n * be configured to hold, so a host runs one convention instead of two (D186).\n */\nconst SECTIONS = ['id', 'imports', 'requires', 'own', 'exports', 'provides', 'when', 'body'];\n\nfunction sectionIndex(property: TSESTree.ObjectLiteralElement): number {\n const name = propertyName(property);\n\n return name === undefined ? -1 : SECTIONS.indexOf(name);\n}\n\n/** The declared sections in source order, or `undefined` when a key is not a section this rule can place. */\nfunction sectionOrder(properties: Properties): readonly number[] | undefined {\n const order: number[] = [];\n\n for (const property of properties) {\n const index = sectionIndex(property);\n\n if (index < 0) return undefined;\n\n order.push(index);\n }\n\n return order;\n}\n\nfunction firstMisplaced(order: readonly number[]): number {\n let previous = -1;\n\n for (const [index, value] of order.entries()) {\n if (value < previous) return index;\n\n previous = value;\n }\n\n return -1;\n}\n\n/**\n * A comment between two sections belongs to neither of them, so reordering would move it away from the line it\n * explains. Such an object is reported without a fix; a comment inside a section travels with it.\n */\nfunction hasSeparatingComment(sourceCode: TSESLint.SourceCode, object: TSESTree.ObjectExpression): boolean {\n return sourceCode\n .getCommentsInside(object)\n .some(\n comment =>\n !object.properties.some(\n property => property.range[0] <= comment.range[0] && comment.range[1] <= property.range[1],\n ),\n );\n}\n\nfunction* reorder(\n fixer: TSESLint.RuleFixer,\n sourceCode: TSESLint.SourceCode,\n properties: Properties,\n): Generator<TSESLint.RuleFix> {\n const sorted = [...properties].sort((left, right) => sectionIndex(left) - sectionIndex(right));\n\n for (const [index, property] of properties.entries()) {\n const replacement = sorted[index];\n\n if (replacement !== undefined && replacement !== property) {\n yield fixer.replaceText(property, sourceCode.getText(replacement));\n }\n }\n}\n\nconst defineFeaturePropertyOrder = createRule<[], MessageIds>({\n create(context) {\n const check = (object: TSESTree.ObjectExpression): void => {\n const order = sectionOrder(object.properties);\n\n if (order === undefined) return;\n\n const misplaced = firstMisplaced(order);\n const property = object.properties[misplaced];\n\n if (property === undefined) return;\n\n context.report({\n fix: hasSeparatingComment(context.sourceCode, object)\n ? null\n : fixer => [...reorder(fixer, context.sourceCode, object.properties)],\n messageId: 'sectionOrder',\n node: property,\n });\n };\n\n return {\n CallExpression(node): void {\n const [options] = node.arguments;\n\n if (calleeName(node) !== 'defineFeature' || options?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n check(options);\n },\n };\n },\n meta: {\n docs: {\n description: 'The sections of `defineFeature` keep the declaration order of the specification.',\n },\n fixable: 'code',\n messages: {\n sectionOrder: `A feature declares its sections in one order: ${SECTIONS.join(', ')} (spec §2.1, D186).`,\n },\n schema: [],\n type: 'layout',\n },\n name: 'define-feature-property-order',\n});\n\nexport { defineFeaturePropertyOrder };\n"],"names":["SECTIONS","sectionIndex","property","name","propertyName","sectionOrder","properties","order","index","firstMisplaced","previous","value","hasSeparatingComment","sourceCode","object","comment","reorder","fixer","sorted","left","right","replacement","defineFeaturePropertyOrder","createRule","context","check","misplaced","node","options","calleeName","AST_NODE_TYPES"],"mappings":"2JAcA,MAAMA,EAAW,CAAC,KAAM,UAAW,WAAY,MAAO,UAAW,WAAY,OAAQ,MAAM,EAE3F,SAASC,EAAaC,EAAuC,CAC3D,MAAMC,EAAOC,EAAaF,CAAQ,EAElC,OAAOC,IAAS,OAAY,GAAKH,EAAS,QAAQG,CAAI,CACxD,CAGA,SAASE,EAAaC,EAAsB,CAC1C,MAAMC,EAAkB,CAAA,EAExB,UAAWL,KAAYI,EAAY,CACjC,MAAME,EAAQP,EAAaC,CAAQ,EAEnC,GAAIM,EAAQ,EAAG,OAEfD,EAAM,KAAKC,CAAK,CAClB,CAEA,OAAOD,CACT,CAEA,SAASE,EAAeF,EAAwB,CAC9C,IAAIG,EAAW,GAEf,SAAW,CAACF,EAAOG,CAAK,IAAKJ,EAAM,QAAO,EAAI,CAC5C,GAAII,EAAQD,EAAU,OAAOF,EAE7BE,EAAWC,CACb,CAEA,MAAO,EACT,CAMA,SAASC,EAAqBC,EAAiCC,EAAiC,CAC9F,OAAOD,EACJ,kBAAkBC,CAAM,EACxB,KACCC,GACE,CAACD,EAAO,WAAW,KACjBZ,GAAYA,EAAS,MAAM,CAAC,GAAKa,EAAQ,MAAM,CAAC,GAAKA,EAAQ,MAAM,CAAC,GAAKb,EAAS,MAAM,CAAC,CAAC,CAC3F,CAET,CAEA,SAAUc,EACRC,EACAJ,EACAP,EAAsB,CAEtB,MAAMY,EAAS,CAAC,GAAGZ,CAAU,EAAE,KAAK,CAACa,EAAMC,IAAUnB,EAAakB,CAAI,EAAIlB,EAAamB,CAAK,CAAC,EAE7F,SAAW,CAACZ,EAAON,CAAQ,IAAKI,EAAW,QAAO,EAAI,CACpD,MAAMe,EAAcH,EAAOV,CAAK,EAE5Ba,IAAgB,QAAaA,IAAgBnB,IAC/C,MAAMe,EAAM,YAAYf,EAAUW,EAAW,QAAQQ,CAAW,CAAC,EAErE,CACF,CAEA,MAAMC,EAA6BC,EAA2B,CAC5D,OAAOC,EAAO,CACZ,MAAMC,EAASX,GAA2C,CACxD,MAAMP,EAAQF,EAAaS,EAAO,UAAU,EAE5C,GAAIP,IAAU,OAAW,OAEzB,MAAMmB,EAAYjB,EAAeF,CAAK,EAChCL,EAAWY,EAAO,WAAWY,CAAS,EAExCxB,IAAa,QAEjBsB,EAAQ,OAAO,CACb,IAAKZ,EAAqBY,EAAQ,WAAYV,CAAM,EAChD,KACAG,GAAS,CAAC,GAAGD,EAAQC,EAAOO,EAAQ,WAAYV,EAAO,UAAU,CAAC,EACtE,UAAW,eACX,KAAMZ,CACP,CAAA,CACH,EAEA,MAAO,CACL,eAAeyB,EAAI,CACjB,KAAM,CAACC,CAAO,EAAID,EAAK,UAEnBE,EAAWF,CAAI,IAAM,iBAAmBC,GAAS,OAASE,EAAe,kBAE7EL,EAAMG,CAAO,CACf,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,kFACd,EACD,QAAS,OACT,SAAU,CACR,aAAc,iDAAiD5B,EAAS,KAAK,IAAI,CAAC,wBACnF,EACD,OAAQ,CAAA,EACR,KAAM,QACP,EACD,KAAM,+BACP,CAAA"}
@@ -0,0 +1,8 @@
1
+ type MessageIds = 'invalidId' | 'missingPrefix';
2
+ type Options = readonly [{
3
+ readonly prefix?: string;
4
+ }];
5
+ declare const idNaming: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
6
+ name: string;
7
+ };
8
+ export { idNaming };
@@ -0,0 +1,2 @@
1
+ import{declarationOf as n}from"../declarations.js";import{createRule as s}from"../rule.js";import{staticValueOf as d}from"../static-value.js";const o=/^[A-Za-z][A-Za-z0-9]*(?:[./:-][A-Za-z0-9]+)*$/,l=160,c=/[./:-]/;function m(e){const[i]=e.split(c);return i??e}const p=s({create(e,[{prefix:i}]){return{CallExpression(a){const r=n(a);if(r===void 0)return;const t=d(r.id,e.sourceCode.getScope(r.id));if(t.kind==="literal"){if(t.value.length>l||!o.test(t.value)){e.report({data:{id:t.value},messageId:"invalidId",node:r.id});return}i!==void 0&&m(t.value)!==i&&e.report({data:{id:t.value,prefix:i},messageId:"missingPrefix",node:r.id})}}}},meta:{defaultOptions:[{}],docs:{description:"A declaration id follows the grammar the runtime accepts, and the prefix a project reserved."},messages:{invalidId:'The id "{{id}}" is refused when the declaration runs: an id starts with a letter and joins segments of letters and digits with one of `.`, `/`, `:`, `-`, within 160 characters.',missingPrefix:'The id "{{id}}" must open with the segment "{{prefix}}" this project reserved.'},schema:[{additionalProperties:!1,properties:{prefix:{type:"string"}},type:"object"}],type:"problem"},name:"id-naming"});export{p as idNaming};
2
+ //# sourceMappingURL=id-naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-naming.js","sources":["../../src/rules/id-naming.ts"],"sourcesContent":["import { declarationOf } from '../declarations';\nimport { createRule } from '../rule';\nimport { staticValueOf } from '../static-value';\n\ntype MessageIds = 'invalidId' | 'missingPrefix';\ntype Options = readonly [{ readonly prefix?: string }];\n\n/**\n * The grammar the runtime accepts, held here in the same shape the core declares it: a letter, then segments of\n * letters and digits joined by one separator each. A longer or otherwise shaped id is a `DeclarationError` at\n * declaration time; this rule says so before the code runs.\n */\nconst ID_PATTERN = /^[A-Za-z][A-Za-z0-9]*(?:[./:-][A-Za-z0-9]+)*$/;\nconst MAX_LENGTH = 160;\nconst SEPARATORS = /[./:-]/;\n\nfunction firstSegment(id: string): string {\n const [segment] = id.split(SEPARATORS);\n\n return segment ?? id;\n}\n\nconst idNaming = createRule<Options, MessageIds>({\n create(context, [{ prefix }]) {\n return {\n CallExpression(node): void {\n const declaration = declarationOf(node);\n\n if (declaration === undefined) return;\n\n const value = staticValueOf(declaration.id, context.sourceCode.getScope(declaration.id));\n\n if (value.kind !== 'literal') return;\n\n if (value.value.length > MAX_LENGTH || !ID_PATTERN.test(value.value)) {\n context.report({ data: { id: value.value }, messageId: 'invalidId', node: declaration.id });\n\n return;\n }\n\n if (prefix !== undefined && firstSegment(value.value) !== prefix) {\n context.report({ data: { id: value.value, prefix }, messageId: 'missingPrefix', node: declaration.id });\n }\n },\n };\n },\n meta: {\n defaultOptions: [{}],\n docs: {\n description: 'A declaration id follows the grammar the runtime accepts, and the prefix a project reserved.',\n },\n messages: {\n invalidId:\n 'The id \"{{id}}\" is refused when the declaration runs: an id starts with a letter and joins segments of letters and digits with one of `.`, `/`, `:`, `-`, within 160 characters.',\n missingPrefix: 'The id \"{{id}}\" must open with the segment \"{{prefix}}\" this project reserved.',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n prefix: { type: 'string' },\n },\n type: 'object',\n },\n ],\n type: 'problem',\n },\n name: 'id-naming',\n});\n\nexport { idNaming };\n"],"names":["ID_PATTERN","MAX_LENGTH","SEPARATORS","firstSegment","id","segment","idNaming","createRule","context","prefix","node","declaration","declarationOf","value","staticValueOf"],"mappings":"8IAYA,MAAMA,EAAa,gDACbC,EAAa,IACbC,EAAa,SAEnB,SAASC,EAAaC,EAAU,CAC9B,KAAM,CAACC,CAAO,EAAID,EAAG,MAAMF,CAAU,EAErC,OAAOG,GAAWD,CACpB,CAEA,MAAME,EAAWC,EAAgC,CAC/C,OAAOC,EAAS,CAAC,CAAE,OAAAC,CAAM,CAAE,EAAC,CAC1B,MAAO,CACL,eAAeC,EAAI,CACjB,MAAMC,EAAcC,EAAcF,CAAI,EAEtC,GAAIC,IAAgB,OAAW,OAE/B,MAAME,EAAQC,EAAcH,EAAY,GAAIH,EAAQ,WAAW,SAASG,EAAY,EAAE,CAAC,EAEvF,GAAIE,EAAM,OAAS,UAEnB,IAAIA,EAAM,MAAM,OAASZ,GAAc,CAACD,EAAW,KAAKa,EAAM,KAAK,EAAG,CACpEL,EAAQ,OAAO,CAAE,KAAM,CAAE,GAAIK,EAAM,KAAK,EAAI,UAAW,YAAa,KAAMF,EAAY,EAAE,CAAE,EAE1F,MACF,CAEIF,IAAW,QAAaN,EAAaU,EAAM,KAAK,IAAMJ,GACxDD,EAAQ,OAAO,CAAE,KAAM,CAAE,GAAIK,EAAM,MAAO,OAAAJ,CAAM,EAAI,UAAW,gBAAiB,KAAME,EAAY,EAAE,CAAE,EAE1G,EAEJ,EACA,KAAM,CACJ,eAAgB,CAAC,CAAA,CAAE,EACnB,KAAM,CACJ,YAAa,8FACd,EACD,SAAU,CACR,UACE,mLACF,cAAe,gFAChB,EACD,OAAQ,CACN,CACE,qBAAsB,GACtB,WAAY,CACV,OAAQ,CAAE,KAAM,QAAQ,CACzB,EACD,KAAM,QACP,CACF,EACD,KAAM,SACP,EACD,KAAM,WACP,CAAA"}
@@ -0,0 +1,8 @@
1
+ type Layer = 'integration' | 'models' | 'ui';
2
+ type Options = readonly [{
3
+ readonly layer?: Layer;
4
+ }];
5
+ declare const layerPlacement: import("@typescript-eslint/utils/ts-eslint").RuleModule<"misplacedDeclaration", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
6
+ name: string;
7
+ };
8
+ export { layerPlacement };
@@ -0,0 +1,2 @@
1
+ import{calleeName as o}from"../ast.js";import{createRule as r}from"../rule.js";const d=new Map([["defineCondition",["integration","ui"]],["defineFeature",["integration"]],["defineHostContract",["integration","ui"]],["defineModel",["models","ui"]],["definePipe",["integration","ui"]],["definePort",["integration","ui"]],["defineRegistry",["integration","ui"]],["defineSlot",["integration","ui"]],["defineSwitchSlot",["integration","ui"]]]),l=r({create(a,[{layer:i}]){return i===void 0?{}:{CallExpression(n){const e=o(n),t=e===void 0?void 0:d.get(e);e===void 0||t===void 0||t.includes(i)||a.report({data:{allowed:t.join(" or "),declaration:e,layer:i},messageId:"misplacedDeclaration",node:n.callee})}}},meta:{defaultOptions:[{}],docs:{description:"Each declaration is written in the layer that owns it, as the host named its layers."},messages:{misplacedDeclaration:"{{declaration}} is written in the {{layer}} layer, and it belongs to {{allowed}}."},schema:[{additionalProperties:!1,properties:{layer:{enum:["integration","models","ui"],type:"string"}},type:"object"}],type:"problem"},name:"layer-placement"});export{l as layerPlacement};
2
+ //# sourceMappingURL=layer-placement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer-placement.js","sources":["../../src/rules/layer-placement.ts"],"sourcesContent":["import { calleeName } from '../ast';\nimport { createRule } from '../rule';\n\ntype Layer = 'integration' | 'models' | 'ui';\ntype MessageIds = 'misplacedDeclaration';\ntype Options = readonly [{ readonly layer?: Layer }];\n\n/**\n * Where each declaration belongs, in the layout the guide describes: a feature composes, so it lives in the\n * integration layer; a model is its own layer or a UI contract of the feature that grants it; a contract is written\n * by the side that owns the extension point, which is the integration layer or the UI contracts beside it.\n */\nconst PLACEMENT = new Map<string, readonly Layer[]>([\n ['defineCondition', ['integration', 'ui']],\n ['defineFeature', ['integration']],\n ['defineHostContract', ['integration', 'ui']],\n ['defineModel', ['models', 'ui']],\n ['definePipe', ['integration', 'ui']],\n ['definePort', ['integration', 'ui']],\n ['defineRegistry', ['integration', 'ui']],\n ['defineSlot', ['integration', 'ui']],\n ['defineSwitchSlot', ['integration', 'ui']],\n]);\n\nconst layerPlacement = createRule<Options, MessageIds>({\n create(context, [{ layer }]) {\n if (layer === undefined) return {};\n\n return {\n CallExpression(node): void {\n const name = calleeName(node);\n const allowed = name === undefined ? undefined : PLACEMENT.get(name);\n\n if (name === undefined || allowed === undefined || allowed.includes(layer)) return;\n\n context.report({\n data: { allowed: allowed.join(' or '), declaration: name, layer },\n messageId: 'misplacedDeclaration',\n node: node.callee,\n });\n },\n };\n },\n meta: {\n defaultOptions: [{}],\n docs: {\n description: 'Each declaration is written in the layer that owns it, as the host named its layers.',\n },\n messages: {\n misplacedDeclaration: '{{declaration}} is written in the {{layer}} layer, and it belongs to {{allowed}}.',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n layer: { enum: ['integration', 'models', 'ui'], type: 'string' },\n },\n type: 'object',\n },\n ],\n type: 'problem',\n },\n name: 'layer-placement',\n});\n\nexport { layerPlacement };\n"],"names":["PLACEMENT","layerPlacement","createRule","context","layer","node","name","calleeName","allowed"],"mappings":"+EAYA,MAAMA,EAAY,IAAI,IAA8B,CAClD,CAAC,kBAAmB,CAAC,cAAe,IAAI,CAAC,EACzC,CAAC,gBAAiB,CAAC,aAAa,CAAC,EACjC,CAAC,qBAAsB,CAAC,cAAe,IAAI,CAAC,EAC5C,CAAC,cAAe,CAAC,SAAU,IAAI,CAAC,EAChC,CAAC,aAAc,CAAC,cAAe,IAAI,CAAC,EACpC,CAAC,aAAc,CAAC,cAAe,IAAI,CAAC,EACpC,CAAC,iBAAkB,CAAC,cAAe,IAAI,CAAC,EACxC,CAAC,aAAc,CAAC,cAAe,IAAI,CAAC,EACpC,CAAC,mBAAoB,CAAC,cAAe,IAAI,CAAC,CAC3C,CAAA,EAEKC,EAAiBC,EAAgC,CACrD,OAAOC,EAAS,CAAC,CAAE,MAAAC,CAAK,CAAE,EAAC,CACzB,OAAIA,IAAU,OAAkB,CAAA,EAEzB,CACL,eAAeC,EAAI,CACjB,MAAMC,EAAOC,EAAWF,CAAI,EACtBG,EAAUF,IAAS,OAAY,OAAYN,EAAU,IAAIM,CAAI,EAE/DA,IAAS,QAAaE,IAAY,QAAaA,EAAQ,SAASJ,CAAK,GAEzED,EAAQ,OAAO,CACb,KAAM,CAAE,QAASK,EAAQ,KAAK,MAAM,EAAG,YAAaF,EAAM,MAAAF,CAAK,EAC/D,UAAW,uBACX,KAAMC,EAAK,MACZ,CAAA,CACH,EAEJ,EACA,KAAM,CACJ,eAAgB,CAAC,CAAA,CAAE,EACnB,KAAM,CACJ,YAAa,sFACd,EACD,SAAU,CACR,qBAAsB,mFACvB,EACD,OAAQ,CACN,CACE,qBAAsB,GACtB,WAAY,CACV,MAAO,CAAE,KAAM,CAAC,cAAe,SAAU,IAAI,EAAG,KAAM,QAAQ,CAC/D,EACD,KAAM,QACP,CACF,EACD,KAAM,SACP,EACD,KAAM,iBACP,CAAA"}
@@ -0,0 +1,4 @@
1
+ declare const noSnapshotReadInRender: import("@typescript-eslint/utils/ts-eslint").RuleModule<"snapshotInRender", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
2
+ name: string;
3
+ };
4
+ export { noSnapshotReadInRender };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as o}from"@typescript-eslint/utils";import{functionName as a}from"../ast.js";import{createRule as i}from"../rule.js";const p=/^use[A-Z]/,c=/^[A-Z]/;function d(t){const{callee:n}=t;return n.type!==o.MemberExpression||n.computed?!1:n.property.type===o.Identifier&&n.property.name==="getSnapshot"}function m(t){return t.name===void 0?!1:p.test(t.name)?!0:t.hasJsx&&c.test(t.name)}const f=i({create(t){const n=[],r=[];return{":function"(e){const s={hasJsx:!1,name:a(e),reads:[]};n.push(s),r.push(s)},":function:exit"(){r.pop()},CallExpression(e){d(e)&&r.at(-1)?.reads.push(e)},"JSXElement, JSXFragment"(){for(const e of r)e.hasJsx=!0},"Program:exit"(){for(const e of n)if(m(e))for(const s of e.reads)t.report({messageId:"snapshotInRender",node:s})}}},meta:{docs:{description:"A component reads observed data reactively, not by taking a snapshot while it renders."},messages:{snapshotInRender:"A snapshot taken while rendering is not reactive: the component keeps the value it read and never hears the next one. Read it with `useReadable`, or select it in `useModel(Declaration, (model, { read }) => ...)` (D205, D214)."},schema:[],type:"problem"},name:"no-snapshot-read-in-render"});export{f as noSnapshotReadInRender};
2
+ //# sourceMappingURL=no-snapshot-read-in-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-snapshot-read-in-render.js","sources":["../../src/rules/no-snapshot-read-in-render.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { functionName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'snapshotInRender';\n\ntype Frame = {\n hasJsx: boolean;\n readonly name: string | undefined;\n readonly reads: TSESTree.CallExpression[];\n};\n\nconst HOOK_NAME = /^use[A-Z]/;\nconst COMPONENT_NAME = /^[A-Z]/;\n\nfunction isSnapshotRead(node: TSESTree.CallExpression): boolean {\n const { callee } = node;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false;\n\n return callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === 'getSnapshot';\n}\n\n/**\n * A hook by its name, a component by its name and the elements it returns. A function that returns no element is\n * not a render scope even when its name is capitalized, so a factory keeps its snapshot reads.\n */\nfunction isRenderScope(frame: Frame): boolean {\n if (frame.name === undefined) return false;\n\n if (HOOK_NAME.test(frame.name)) return true;\n\n return frame.hasJsx && COMPONENT_NAME.test(frame.name);\n}\n\nconst noSnapshotReadInRender = createRule<[], MessageIds>({\n create(context) {\n const frames: Frame[] = [];\n const stack: Frame[] = [];\n\n return {\n ':function'(node: TSESTree.Node): void {\n const frame: Frame = { hasJsx: false, name: functionName(node), reads: [] };\n\n frames.push(frame);\n stack.push(frame);\n },\n ':function:exit'(): void {\n stack.pop();\n },\n CallExpression(node): void {\n if (isSnapshotRead(node)) stack.at(-1)?.reads.push(node);\n },\n 'JSXElement, JSXFragment'(): void {\n // The elements of a nested callback are still the elements this component renders.\n for (const frame of stack) frame.hasJsx = true;\n },\n 'Program:exit'(): void {\n for (const frame of frames) {\n if (!isRenderScope(frame)) continue;\n\n for (const read of frame.reads) context.report({ messageId: 'snapshotInRender', node: read });\n }\n },\n };\n },\n meta: {\n docs: {\n description: 'A component reads observed data reactively, not by taking a snapshot while it renders.',\n },\n messages: {\n snapshotInRender:\n 'A snapshot taken while rendering is not reactive: the component keeps the value it read and never hears the next one. Read it with `useReadable`, or select it in `useModel(Declaration, (model, { read }) => ...)` (D205, D214).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-snapshot-read-in-render',\n});\n\nexport { noSnapshotReadInRender };\n"],"names":["HOOK_NAME","COMPONENT_NAME","isSnapshotRead","node","callee","AST_NODE_TYPES","isRenderScope","frame","noSnapshotReadInRender","createRule","context","frames","stack","functionName","read"],"mappings":"2IAcA,MAAMA,EAAY,YACZC,EAAiB,SAEvB,SAASC,EAAeC,EAA6B,CACnD,KAAM,CAAE,OAAAC,CAAM,EAAKD,EAEnB,OAAIC,EAAO,OAASC,EAAe,kBAAoBD,EAAO,SAAiB,GAExEA,EAAO,SAAS,OAASC,EAAe,YAAcD,EAAO,SAAS,OAAS,aACxF,CAMA,SAASE,EAAcC,EAAY,CACjC,OAAIA,EAAM,OAAS,OAAkB,GAEjCP,EAAU,KAAKO,EAAM,IAAI,EAAU,GAEhCA,EAAM,QAAUN,EAAe,KAAKM,EAAM,IAAI,CACvD,CAEA,MAAMC,EAAyBC,EAA2B,CACxD,OAAOC,EAAO,CACZ,MAAMC,EAAkB,CAAA,EAClBC,EAAiB,CAAA,EAEvB,MAAO,CACL,YAAYT,EAAmB,CAC7B,MAAMI,EAAe,CAAE,OAAQ,GAAO,KAAMM,EAAaV,CAAI,EAAG,MAAO,EAAE,EAEzEQ,EAAO,KAAKJ,CAAK,EACjBK,EAAM,KAAKL,CAAK,CAClB,EACA,kBAAgB,CACdK,EAAM,IAAG,CACX,EACA,eAAeT,EAAI,CACbD,EAAeC,CAAI,GAAGS,EAAM,GAAG,EAAE,GAAG,MAAM,KAAKT,CAAI,CACzD,EACA,2BAAyB,CAEvB,UAAWI,KAASK,EAAOL,EAAM,OAAS,EAC5C,EACA,gBAAc,CACZ,UAAWA,KAASI,EAClB,GAAKL,EAAcC,CAAK,EAExB,UAAWO,KAAQP,EAAM,MAAOG,EAAQ,OAAO,CAAE,UAAW,mBAAoB,KAAMI,EAAM,CAEhG,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,wFACd,EACD,SAAU,CACR,iBACE,mOACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,4BACP,CAAA"}
@@ -0,0 +1,4 @@
1
+ declare const noSubscribeOutsideModels: import("@typescript-eslint/utils/ts-eslint").RuleModule<"handWrittenSubscription", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
2
+ name: string;
3
+ };
4
+ export { noSubscribeOutsideModels };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as r}from"@typescript-eslint/utils";import{createRule as s}from"../rule.js";function n(t){const{callee:e}=t;return e.type!==r.MemberExpression||e.computed||t.arguments.length===0?!1:e.property.type===r.Identifier&&e.property.name==="subscribe"}const o=s({create(t){return{CallExpression(e){n(e)&&t.report({messageId:"handWrittenSubscription",node:e.callee})}}},meta:{docs:{description:"A hand-written subscription stays in the layer that owns the state it observes."},messages:{handWrittenSubscription:"A subscription written by hand owns a cleanup nobody here can see. In a component read with `useReadable` or a model selection; in a feature or a model declare `effect`, `event` or `stream`."},schema:[],type:"problem"},name:"no-subscribe-outside-models"});export{o as noSubscribeOutsideModels};
2
+ //# sourceMappingURL=no-subscribe-outside-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-subscribe-outside-models.js","sources":["../../src/rules/no-subscribe-outside-models.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { createRule } from '../rule';\n\ntype MessageIds = 'handWrittenSubscription';\n\n/** A call, not a reference: `useSyncExternalStore(source.subscribe, …)` hands the function over without calling it. */\nfunction isSubscribeCall(node: TSESTree.CallExpression): boolean {\n const { callee } = node;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed || node.arguments.length === 0) return false;\n\n return callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === 'subscribe';\n}\n\nconst noSubscribeOutsideModels = createRule<[], MessageIds>({\n create(context) {\n return {\n CallExpression(node): void {\n if (isSubscribeCall(node)) {\n context.report({ messageId: 'handWrittenSubscription', node: node.callee });\n }\n },\n };\n },\n meta: {\n docs: {\n description: 'A hand-written subscription stays in the layer that owns the state it observes.',\n },\n messages: {\n handWrittenSubscription:\n 'A subscription written by hand owns a cleanup nobody here can see. In a component read with `useReadable` or a model selection; in a feature or a model declare `effect`, `event` or `stream`.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-subscribe-outside-models',\n});\n\nexport { noSubscribeOutsideModels };\n"],"names":["isSubscribeCall","node","callee","AST_NODE_TYPES","noSubscribeOutsideModels","createRule","context"],"mappings":"kGAQA,SAASA,EAAgBC,EAA6B,CACpD,KAAM,CAAE,OAAAC,CAAM,EAAKD,EAEnB,OAAIC,EAAO,OAASC,EAAe,kBAAoBD,EAAO,UAAYD,EAAK,UAAU,SAAW,EAAU,GAEvGC,EAAO,SAAS,OAASC,EAAe,YAAcD,EAAO,SAAS,OAAS,WACxF,CAEA,MAAME,EAA2BC,EAA2B,CAC1D,OAAOC,EAAO,CACZ,MAAO,CACL,eAAeL,EAAI,CACbD,EAAgBC,CAAI,GACtBK,EAAQ,OAAO,CAAE,UAAW,0BAA2B,KAAML,EAAK,OAAQ,CAE9E,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,iFACd,EACD,SAAU,CACR,wBACE,gMACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,6BACP,CAAA"}
@@ -0,0 +1,7 @@
1
+ type Options = readonly [{
2
+ readonly threshold: number;
3
+ }];
4
+ declare const preferModelSelection: import("@typescript-eslint/utils/ts-eslint").RuleModule<"separateHooks", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export { preferModelSelection };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as a,ASTUtils as c}from"@typescript-eslint/utils";import{calleeName as u,memberRoot as f}from"../ast.js";import{createRule as l}from"../rule.js";const m=new Set(["useCommand","useReadable"]);function p(e){const{parent:n}=e;if(!(u(e)!=="useModel"||e.arguments.length!==1)&&!(n.type!==a.VariableDeclarator||n.id.type!==a.Identifier))return n}function h(e){const n=u(e),[o]=e.arguments;if(n===void 0||!m.has(n)||o?.type!==a.MemberExpression)return;const s=f(o);return s.type===a.Identifier?s:void 0}const g=l({create(e,[{threshold:n}]){const o=new Map,s=t=>{const r=h(t);if(r===void 0)return;const[i]=c.findVariable(e.sourceCode.getScope(r),r)?.defs??[],d=i===void 0?void 0:o.get(i.node);d!==void 0&&(d.count+=1)};return{CallExpression(t){const r=p(t);r===void 0?s(t):o.set(r,{count:0,node:t})},"Program:exit"(){for(const t of o.values())t.count<n||e.report({data:{count:t.count},messageId:"separateHooks",node:t.node})}}},meta:{defaultOptions:[{threshold:3}],docs:{description:"One model is read through one selection instead of a hook for each of its fields."},messages:{separateHooks:"This model is read through {{count}} separate hooks: `useModel(Declaration, (model, { read }) => ...)` selects data and commands in one call, with the same authority and the same statuses (D205, D214)."},schema:[{additionalProperties:!1,properties:{threshold:{minimum:2,type:"integer"}},type:"object"}],type:"suggestion"},name:"prefer-model-selection"});export{g as preferModelSelection};
2
+ //# sourceMappingURL=prefer-model-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-model-selection.js","sources":["../../src/rules/prefer-model-selection.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';\n\nimport { calleeName, memberRoot } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'separateHooks';\ntype Options = readonly [{ readonly threshold: number }];\n\ntype Reader = {\n count: number;\n readonly node: TSESTree.CallExpression;\n};\n\nconst FIELD_HOOKS = new Set(['useCommand', 'useReadable']);\n\n/** `const order = useModel(OrderModel)` — the granted model, without a selection of its own. */\nfunction grantedModelDeclarator(node: TSESTree.CallExpression): TSESTree.VariableDeclarator | undefined {\n const { parent } = node;\n\n if (calleeName(node) !== 'useModel' || node.arguments.length !== 1) return undefined;\n\n if (parent.type !== AST_NODE_TYPES.VariableDeclarator || parent.id.type !== AST_NODE_TYPES.Identifier) {\n return undefined;\n }\n\n return parent;\n}\n\n/** The identifier a field hook reads its argument from: `useReadable(order.amount)` names `order`. */\nfunction readIdentifier(node: TSESTree.CallExpression): TSESTree.Identifier | undefined {\n const name = calleeName(node);\n const [argument] = node.arguments;\n\n if (name === undefined || !FIELD_HOOKS.has(name) || argument?.type !== AST_NODE_TYPES.MemberExpression) {\n return undefined;\n }\n\n const root = memberRoot(argument);\n\n return root.type === AST_NODE_TYPES.Identifier ? root : undefined;\n}\n\nconst preferModelSelection = createRule<Options, MessageIds>({\n create(context, [{ threshold }]) {\n // Keyed by the declaration a name resolves to, so two components with the same variable name stay apart.\n const readers = new Map<TSESTree.Node, Reader>();\n\n const count = (node: TSESTree.CallExpression): void => {\n const identifier = readIdentifier(node);\n\n if (identifier === undefined) return;\n\n const [definition] = ASTUtils.findVariable(context.sourceCode.getScope(identifier), identifier)?.defs ?? [];\n const reader = definition === undefined ? undefined : readers.get(definition.node);\n\n if (reader !== undefined) reader.count += 1;\n };\n\n return {\n CallExpression(node): void {\n const declarator = grantedModelDeclarator(node);\n\n if (declarator === undefined) count(node);\n else readers.set(declarator, { count: 0, node });\n },\n 'Program:exit'(): void {\n for (const reader of readers.values()) {\n if (reader.count < threshold) continue;\n\n context.report({\n data: { count: reader.count },\n messageId: 'separateHooks',\n node: reader.node,\n });\n }\n },\n };\n },\n meta: {\n defaultOptions: [{ threshold: 3 }],\n docs: {\n description: 'One model is read through one selection instead of a hook for each of its fields.',\n },\n messages: {\n separateHooks:\n 'This model is read through {{count}} separate hooks: `useModel(Declaration, (model, { read }) => ...)` selects data and commands in one call, with the same authority and the same statuses (D205, D214).',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n threshold: { minimum: 2, type: 'integer' },\n },\n type: 'object',\n },\n ],\n type: 'suggestion',\n },\n name: 'prefer-model-selection',\n});\n\nexport { preferModelSelection };\n"],"names":["FIELD_HOOKS","grantedModelDeclarator","node","parent","calleeName","AST_NODE_TYPES","readIdentifier","name","argument","root","memberRoot","preferModelSelection","createRule","context","threshold","readers","count","identifier","definition","ASTUtils","reader","declarator"],"mappings":"uKAcA,MAAMA,EAAc,IAAI,IAAI,CAAC,aAAc,aAAa,CAAC,EAGzD,SAASC,EAAuBC,EAA6B,CAC3D,KAAM,CAAE,OAAAC,CAAM,EAAKD,EAEnB,GAAI,EAAAE,EAAWF,CAAI,IAAM,YAAcA,EAAK,UAAU,SAAW,IAE7D,EAAAC,EAAO,OAASE,EAAe,oBAAsBF,EAAO,GAAG,OAASE,EAAe,YAI3F,OAAOF,CACT,CAGA,SAASG,EAAeJ,EAA6B,CACnD,MAAMK,EAAOH,EAAWF,CAAI,EACtB,CAACM,CAAQ,EAAIN,EAAK,UAExB,GAAIK,IAAS,QAAa,CAACP,EAAY,IAAIO,CAAI,GAAKC,GAAU,OAASH,EAAe,iBACpF,OAGF,MAAMI,EAAOC,EAAWF,CAAQ,EAEhC,OAAOC,EAAK,OAASJ,EAAe,WAAaI,EAAO,MAC1D,CAEA,MAAME,EAAuBC,EAAgC,CAC3D,OAAOC,EAAS,CAAC,CAAE,UAAAC,CAAS,CAAE,EAAC,CAE7B,MAAMC,EAAU,IAAI,IAEdC,EAASd,GAAuC,CACpD,MAAMe,EAAaX,EAAeJ,CAAI,EAEtC,GAAIe,IAAe,OAAW,OAE9B,KAAM,CAACC,CAAU,EAAIC,EAAS,aAAaN,EAAQ,WAAW,SAASI,CAAU,EAAGA,CAAU,GAAG,MAAQ,CAAA,EACnGG,EAASF,IAAe,OAAY,OAAYH,EAAQ,IAAIG,EAAW,IAAI,EAE7EE,IAAW,SAAWA,EAAO,OAAS,EAC5C,EAEA,MAAO,CACL,eAAelB,EAAI,CACjB,MAAMmB,EAAapB,EAAuBC,CAAI,EAE1CmB,IAAe,OAAWL,EAAMd,CAAI,EACnCa,EAAQ,IAAIM,EAAY,CAAE,MAAO,EAAG,KAAAnB,EAAM,CACjD,EACA,gBAAc,CACZ,UAAWkB,KAAUL,EAAQ,SACvBK,EAAO,MAAQN,GAEnBD,EAAQ,OAAO,CACb,KAAM,CAAE,MAAOO,EAAO,KAAK,EAC3B,UAAW,gBACX,KAAMA,EAAO,IACd,CAAA,CAEL,EAEJ,EACA,KAAM,CACJ,eAAgB,CAAC,CAAE,UAAW,EAAG,EACjC,KAAM,CACJ,YAAa,mFACd,EACD,SAAU,CACR,cACE,2MACH,EACD,OAAQ,CACN,CACE,qBAAsB,GACtB,WAAY,CACV,UAAW,CAAE,QAAS,EAAG,KAAM,SAAS,CACzC,EACD,KAAM,QACP,CACF,EACD,KAAM,YACP,EACD,KAAM,wBACP,CAAA"}
@@ -0,0 +1,5 @@
1
+ type MessageIds = 'missingWrapper' | 'undeclaredUiModel';
2
+ declare const requireDeclaredModels: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
3
+ name: string;
4
+ };
5
+ export { requireDeclaredModels };
@@ -0,0 +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};
2
+ //# sourceMappingURL=require-declared-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-declared-models.js","sources":["../../src/rules/require-declared-models.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty } from '../ast';\nimport type { FunctionNode, ModelBinding } from '../model-bindings';\nimport { enclosingFunction, isApi, isFunction, isSlot, localValue, modelBinding, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'missingWrapper' | 'undeclaredUiModel';\ntype Requirements = ReadonlySet<ModelBinding> | undefined;\n\nfunction plannedModel(value: TSESTree.Node): TSESTree.Node | undefined {\n if (value.type === AST_NODE_TYPES.CallExpression) return value.arguments[0];\n if (value.type === AST_NODE_TYPES.ArrayExpression) return value.elements[0] ?? undefined;\n return undefined;\n}\n\nconst requireDeclaredModels = createRule<readonly [], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const calls: TSESTree.CallExpression[] = [];\n const returns = new Map<FunctionNode, TSESTree.Node[]>();\n const wrappers = new Map<TSESTree.Node, Requirements>();\n const readers = new Map<FunctionNode, Requirements>();\n const granted = new Set<ModelBinding>();\n\n const api = (node: TSESTree.Node, name: string): boolean => isApi(source, node, '@opetope/react', name);\n\n const requirements = (node: TSESTree.Node | undefined): Requirements => {\n if (node === undefined) return undefined;\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n const bindings = new Set<ModelBinding>();\n\n for (const element of value.elements) {\n if (element === null) continue;\n\n const binding = modelBinding(source, element);\n\n if (binding === undefined) return undefined;\n\n bindings.add(binding);\n }\n\n return bindings;\n };\n\n const collectWrapper = (node: TSESTree.CallExpression): void => {\n const inner = unwrap(node.callee);\n\n if (inner.type !== AST_NODE_TYPES.CallExpression || !api(inner.callee, 'requiresModels')) return;\n\n const models = requirements(inner.arguments[0]);\n\n wrappers.set(node, models);\n const [argument] = node.arguments;\n\n if (argument === undefined) return;\n\n const component = localValue(source, argument);\n\n if (isFunction(component)) readers.set(component, models);\n };\n\n const grantModels = (node: TSESTree.ArrayExpression): void => {\n for (const element of node.elements) {\n if (element === null) continue;\n\n const value = localValue(source, element);\n const model = plannedModel(value);\n const binding = model === undefined ? undefined : modelBinding(source, model);\n\n if (binding !== undefined) granted.add(binding);\n }\n };\n\n const declaredComponent = (value: TSESTree.Node): boolean =>\n wrappers.has(value) || (isFunction(value) && readers.has(value));\n\n const checkComponent = (component: TSESTree.Property): void => {\n const value = localValue(source, component.value);\n if (declaredComponent(value)) return;\n // Imported components and parameters are unknown, not verified or rejected by this single-module rule.\n if (value.type === AST_NODE_TYPES.Identifier || value.type === AST_NODE_TYPES.MemberExpression) return;\n context.report({ messageId: 'missingWrapper', node: component });\n };\n\n const checkContribution = (object: TSESTree.ObjectExpression): void => {\n const component = findProperty(object, 'Component');\n const models = findProperty(object, 'models');\n\n if (component === undefined || models === undefined) return;\n\n const plans = localValue(source, models.value);\n\n if (plans.type !== AST_NODE_TYPES.ArrayExpression || plans.elements.length === 0) return;\n\n grantModels(plans);\n checkComponent(component);\n };\n\n const returnedValues = (value: FunctionNode): readonly TSESTree.Node[] => {\n if (value.body === undefined) return [];\n return value.body.type === AST_NODE_TYPES.BlockStatement ? (returns.get(value) ?? []) : [value.body];\n };\n\n const contributionValues = (node: TSESTree.Node, seen = new Set<TSESTree.Node>()): void => {\n const value = localValue(source, node);\n\n if (seen.has(value)) return;\n\n seen.add(value);\n if (value.type === AST_NODE_TYPES.ObjectExpression) checkContribution(value);\n else if (isFunction(value)) {\n for (const returned of returnedValues(value)) contributionValues(returned, seen);\n }\n };\n\n const declaresRead = (node: TSESTree.Node, binding: ModelBinding): boolean => {\n const fn = enclosingFunction(source, node);\n if (fn === undefined || !readers.has(fn)) return false;\n const declared = readers.get(fn);\n return declared === undefined || declared.has(binding);\n };\n\n const checkRead = (node: TSESTree.CallExpression): void => {\n if (!api(node.callee, 'useModel')) return;\n\n const [argument] = node.arguments;\n const binding = argument === undefined ? undefined : modelBinding(source, argument);\n\n if (binding === undefined || !granted.has(binding)) return;\n\n if (declaresRead(node, binding)) return;\n\n context.report({ data: { name: source.getText(argument) }, messageId: 'undeclaredUiModel', node });\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n ReturnStatement(node): void {\n const fn = enclosingFunction(source, node);\n\n if (fn === undefined || node.argument === null) return;\n\n const values = returns.get(fn) ?? [];\n\n values.push(node.argument);\n returns.set(fn, values);\n },\n 'Program:exit'(): void {\n for (const call of calls) collectWrapper(call);\n for (const call of calls) {\n const contribution = call.arguments[1];\n\n if (contribution !== undefined && isSlot(source, call.callee)) contributionValues(contribution);\n }\n for (const call of calls) checkRead(call);\n },\n };\n },\n meta: {\n docs: { description: 'Each visible reader of a per-mount UI model declares it with requiresModels.' },\n messages: {\n missingWrapper:\n 'This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.',\n undeclaredUiModel:\n 'A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'require-declared-models',\n});\n\nexport { requireDeclaredModels };\n"],"names":["plannedModel","value","AST_NODE_TYPES","requireDeclaredModels","createRule","context","source","calls","returns","wrappers","readers","granted","api","node","name","isApi","requirements","localValue","bindings","element","binding","modelBinding","collectWrapper","inner","unwrap","models","argument","component","isFunction","grantModels","model","declaredComponent","checkComponent","checkContribution","object","findProperty","plans","returnedValues","contributionValues","seen","returned","declaresRead","fn","enclosingFunction","declared","checkRead","values","call","contribution","isSlot"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ type Options = readonly [{
2
+ readonly allowInCallees: readonly string[];
3
+ }];
4
+ declare const requireLiteralId: import("@typescript-eslint/utils/ts-eslint").RuleModule<"computedId", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export { requireLiteralId };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as a}from"@typescript-eslint/utils";import{calleeName as n,functionName as o}from"../ast.js";import{declarationOf as l}from"../declarations.js";import{createRule as s}from"../rule.js";import{staticValueOf as d}from"../static-value.js";function c(e){return e.type===a.CallExpression?n(e):o(e)}function u(e,i){let t=e.parent;for(;t!=null;){const r=c(t);if(r!==void 0&&i.includes(r))return!0;t=t.parent}return!1}const m=s({create(e,[{allowInCallees:i}]){return{CallExpression(t){const r=l(t);r===void 0||d(r.id,e.sourceCode.getScope(r.id)).kind!=="computed"||u(t,i)||e.report({data:{declaration:r.name},messageId:"computedId",node:r.id})}}},meta:{defaultOptions:[{allowInCallees:[]}],docs:{description:"A declaration names itself with a string that is written, not built at the call site."},messages:{computedId:"The id of {{declaration}} is built here, so nothing can find the declaration by its name. Write the id as a string, or name the factory that may compute it in `allowInCallees`."},schema:[{additionalProperties:!1,properties:{allowInCallees:{items:{type:"string"},type:"array"}},type:"object"}],type:"problem"},name:"require-literal-id"});export{m as requireLiteralId};
2
+ //# sourceMappingURL=require-literal-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-literal-id.js","sources":["../../src/rules/require-literal-id.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName, functionName } from '../ast';\nimport { declarationOf } from '../declarations';\nimport { createRule } from '../rule';\nimport { staticValueOf } from '../static-value';\n\ntype MessageIds = 'computedId';\ntype Options = readonly [{ readonly allowInCallees: readonly string[] }];\n\n/** A factory names itself either as the function that writes the declaration or as the call that receives it. */\nfunction enclosingName(node: TSESTree.Node): string | undefined {\n if (node.type === AST_NODE_TYPES.CallExpression) return calleeName(node);\n\n return functionName(node);\n}\n\nfunction isInsideAllowedFactory(node: TSESTree.Node, allowed: readonly string[]): boolean {\n // The parent of `Program` is `null`, so the walk ends there rather than at an absent property.\n let current: TSESTree.Node | null | undefined = node.parent;\n\n while (current !== undefined && current !== null) {\n const name = enclosingName(current);\n\n if (name !== undefined && allowed.includes(name)) return true;\n\n current = current.parent;\n }\n\n return false;\n}\n\nconst requireLiteralId = createRule<Options, MessageIds>({\n create(context, [{ allowInCallees }]) {\n return {\n CallExpression(node): void {\n const declaration = declarationOf(node);\n\n if (declaration === undefined) return;\n\n const value = staticValueOf(declaration.id, context.sourceCode.getScope(declaration.id));\n\n if (value.kind !== 'computed' || isInsideAllowedFactory(node, allowInCallees)) return;\n\n context.report({\n data: { declaration: declaration.name },\n messageId: 'computedId',\n node: declaration.id,\n });\n },\n };\n },\n meta: {\n defaultOptions: [{ allowInCallees: [] }],\n docs: {\n description: 'A declaration names itself with a string that is written, not built at the call site.',\n },\n messages: {\n computedId:\n 'The id of {{declaration}} is built here, so nothing can find the declaration by its name. Write the id as a string, or name the factory that may compute it in `allowInCallees`.',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n allowInCallees: { items: { type: 'string' }, type: 'array' },\n },\n type: 'object',\n },\n ],\n type: 'problem',\n },\n name: 'require-literal-id',\n});\n\nexport { requireLiteralId };\n"],"names":["enclosingName","node","AST_NODE_TYPES","calleeName","functionName","isInsideAllowedFactory","allowed","current","name","requireLiteralId","createRule","context","allowInCallees","declaration","declarationOf","staticValueOf"],"mappings":"iQAYA,SAASA,EAAcC,EAAmB,CACxC,OAAIA,EAAK,OAASC,EAAe,eAAuBC,EAAWF,CAAI,EAEhEG,EAAaH,CAAI,CAC1B,CAEA,SAASI,EAAuBJ,EAAqBK,EAA0B,CAE7E,IAAIC,EAA4CN,EAAK,OAErD,KAAgCM,GAAY,MAAM,CAChD,MAAMC,EAAOR,EAAcO,CAAO,EAElC,GAAIC,IAAS,QAAaF,EAAQ,SAASE,CAAI,EAAG,MAAO,GAEzDD,EAAUA,EAAQ,MACpB,CAEA,MAAO,EACT,CAEA,MAAME,EAAmBC,EAAgC,CACvD,OAAOC,EAAS,CAAC,CAAE,eAAAC,CAAc,CAAE,EAAC,CAClC,MAAO,CACL,eAAeX,EAAI,CACjB,MAAMY,EAAcC,EAAcb,CAAI,EAElCY,IAAgB,QAENE,EAAcF,EAAY,GAAIF,EAAQ,WAAW,SAASE,EAAY,EAAE,CAAC,EAE7E,OAAS,YAAcR,EAAuBJ,EAAMW,CAAc,GAE5ED,EAAQ,OAAO,CACb,KAAM,CAAE,YAAaE,EAAY,IAAI,EACrC,UAAW,aACX,KAAMA,EAAY,EACnB,CAAA,CACH,EAEJ,EACA,KAAM,CACJ,eAAgB,CAAC,CAAE,eAAgB,CAAA,EAAI,EACvC,KAAM,CACJ,YAAa,uFACd,EACD,SAAU,CACR,WACE,kLACH,EACD,OAAQ,CACN,CACE,qBAAsB,GACtB,WAAY,CACV,eAAgB,CAAE,MAAO,CAAE,KAAM,QAAQ,EAAI,KAAM,OAAO,CAC3D,EACD,KAAM,QACP,CACF,EACD,KAAM,SACP,EACD,KAAM,oBACP,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'whenIsAsync' | 'whenReturnsBareSource' | 'whenReturnsSource';
3
+ declare const whenPredicate: TSESLint.RuleModule<MessageIds, [], unknown, TSESLint.RuleListener> & {
4
+ name: string;
5
+ };
6
+ export { whenPredicate };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as i}from"@typescript-eslint/utils";import{propertyName as m,isFunctionLike as l,calleeName as y,patternKeys as c,memberRoot as h,memberFirstStep as S}from"../ast.js";import{createRule as I}from"../rule.js";const b=new Set(["pipe","register","slot"]),x=new Set(["exports","imports","own","read"]),w=new Set(["exports","imports","own"]);function T(n){const{parent:e}=n;if(e.type!==i.CallExpression||e.arguments.length<2)return!1;const t=y(e);return t!==void 0&&b.has(t)&&e.arguments.at(-1)===n}function d(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==i.ObjectPattern))return[...c(e)].some(t=>x.has(t))?e:void 0}function g(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==i.Identifier))return e}function A(n,e){const t=h(e),r=S(e);return t.type!==i.Identifier||t.name!==n.name?!1:r!==void 0&&w.has(r)}function E(n,e){const t=h(e);return t.type!==i.Identifier?!1:w.has(t.name)&&c(n).has(t.name)}function R(n,e){const t=d(n);if(t!==void 0)return E(t,e)?"read":void 0;const r=g(n);if(r!==void 0)return A(r,e)?`${r.name}.read`:void 0}function O(n,e){const t=e.properties.find(s=>{const a=m(s);return a===void 0||a>"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===i.Identifier&&t.name===e.name)}function N(n,e){const{parent:t}=n;return t.type===i.ObjectExpression&&T(t)||d(e)!==void 0}const B=I({create(n){const e=(r,s)=>{const a=R(r,s);a!==void 0&&n.report({fix(p){const o=d(r),u=p.replaceText(s,`${a}(${n.sourceCode.getText(s)})`);if(o===void 0||c(o).has("read"))return[u];const f=O(p,o);return f===void 0?[u]:[u,f]},messageId:"whenReturnsSource",node:s})},t=r=>{if(r.async){n.report({messageId:"whenIsAsync",node:r});return}if(r.type!==i.ArrowFunctionExpression)return;const{body:s}=r;if(s.type===i.MemberExpression){e(r,s);return}s.type===i.Identifier&&!C(r,s)&&n.report({messageId:"whenReturnsBareSource",node:s})};return{Property(r){m(r)!=="when"||!l(r.value)||N(r,r.value)&&t(r.value)}}},meta:{docs:{description:"A contribution `when` predicate answers the visibility fact, not the source that carries it."},fixable:"code",messages:{whenIsAsync:"A contribution `when` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).",whenReturnsBareSource:"A contribution `when` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).",whenReturnsSource:"A contribution `when` predicate returns the source instead of the fact; read it with `read(...)` (D220)."},schema:[],type:"problem"},name:"when-predicate"});export{B as whenPredicate};
2
+ //# sourceMappingURL=when-predicate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"when-predicate.js","sources":["../../src/rules/when-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 = 'whenIsAsync' | 'whenReturnsBareSource' | 'whenReturnsSource';\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 isContributionWhen(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 whenPredicate = 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: 'whenReturnsSource',\n node: body,\n });\n };\n\n const check = (fn: FunctionLike): void => {\n if (fn.async) {\n context.report({ messageId: 'whenIsAsync', 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: 'whenReturnsBareSource', node: body });\n }\n };\n\n return {\n Property(node): void {\n if (propertyName(node) !== 'when' || !isFunctionLike(node.value)) return;\n\n if (isContributionWhen(node, node.value)) check(node.value);\n },\n };\n },\n meta: {\n docs: {\n description: 'A contribution `when` predicate answers the visibility fact, not the source that carries it.',\n },\n fixable: 'code',\n messages: {\n whenIsAsync:\n 'A contribution `when` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).',\n whenReturnsBareSource:\n 'A contribution `when` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).',\n whenReturnsSource:\n 'A contribution `when` predicate returns the source instead of the fact; read it with `read(...)` (D220).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'when-predicate',\n});\n\nexport { whenPredicate };\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","isContributionWhen","whenPredicate","createRule","context","reportSource","reader","wrapped","inserted","check","isFunctionLike"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * What a rule can learn about an expression without types: its exact string, the fact that it names something
4
+ * declared elsewhere, or nothing at all because the value is built where it is written.
5
+ */
6
+ type StaticValue = {
7
+ readonly kind: 'computed';
8
+ } | {
9
+ readonly kind: 'external';
10
+ } | {
11
+ readonly kind: 'literal';
12
+ readonly value: string;
13
+ };
14
+ declare function staticValueOf(node: TSESTree.Node, scope: TSESLint.Scope.Scope): StaticValue;
15
+ export { staticValueOf };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as r,ASTUtils as p}from"@typescript-eslint/utils";const o={kind:"computed"},a={kind:"external"},d=4,m=new Set([r.ImportDefaultSpecifier,r.ImportNamespaceSpecifier,r.ImportSpecifier]);function y(e){let t=e;for(;t.type===r.TSAsExpression||t.type===r.TSSatisfiesExpression||t.type===r.TSNonNullExpression;)t=t.expression;return t}function S(e){const[t]=e.quasis,n=e.expressions.length>0?null:t?.value.cooked??null;return n===null?o:{kind:"literal",value:n}}function x(e){return e.type===r.Literal?typeof e.value=="string"?{kind:"literal",value:e.value}:o:e.type===r.TemplateLiteral?S(e):void 0}function E(e){const{init:t,parent:n}=e;return t===null||n.kind!=="const"?o:t}function f(e,t){const[n]=p.findVariable(t,e)?.defs??[],i=n?.node;return i===void 0?o:m.has(i.type)?a:i.type===r.VariableDeclarator?E(i):o}function c(e){return"kind"in e}function T(e,t){let n=e;for(;n.type===r.MemberExpression;)n=n.object;if(n.type!==r.Identifier)return o;const i=f(n,t);return!c(i)||i.kind==="external"?a:o}function k(e,t){let n=e;for(let i=0;i<=d;i+=1){const s=y(n),u=x(s);if(u!==void 0)return u;if(s.type===r.MemberExpression)return T(s,t);if(s.type!==r.Identifier)return o;const l=f(s,t);if(c(l))return l;n=l}return o}export{k as staticValueOf};
2
+ //# sourceMappingURL=static-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-value.js","sources":["../src/static-value.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';\n\n/**\n * What a rule can learn about an expression without types: its exact string, the fact that it names something\n * declared elsewhere, or nothing at all because the value is built where it is written.\n */\ntype StaticValue =\n | { readonly kind: 'computed' }\n | { readonly kind: 'external' }\n | { readonly kind: 'literal'; readonly value: string };\n\nconst COMPUTED: StaticValue = { kind: 'computed' };\nconst EXTERNAL: StaticValue = { kind: 'external' };\n\n/** A chain of `const` aliases stays short in real code; the limit only stops a cycle from spinning. */\nconst MAX_DEPTH = 4;\n\nconst IMPORT_BINDINGS = new Set<string>([\n AST_NODE_TYPES.ImportDefaultSpecifier,\n AST_NODE_TYPES.ImportNamespaceSpecifier,\n AST_NODE_TYPES.ImportSpecifier,\n]);\n\nfunction unwrap(node: TSESTree.Node): TSESTree.Node {\n let current = node;\n\n while (\n current.type === AST_NODE_TYPES.TSAsExpression ||\n current.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n current.type === AST_NODE_TYPES.TSNonNullExpression\n ) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction templateOf(node: TSESTree.TemplateLiteral): StaticValue {\n const [quasi] = node.quasis;\n const cooked = node.expressions.length > 0 ? null : (quasi?.value.cooked ?? null);\n\n return cooked === null ? COMPUTED : { kind: 'literal', value: cooked };\n}\n\nfunction literalOf(node: TSESTree.Node): StaticValue | undefined {\n if (node.type === AST_NODE_TYPES.Literal) {\n return typeof node.value === 'string' ? { kind: 'literal', value: node.value } : COMPUTED;\n }\n\n return node.type === AST_NODE_TYPES.TemplateLiteral ? templateOf(node) : undefined;\n}\n\nfunction constantInit(node: TSESTree.VariableDeclarator): StaticValue | TSESTree.Node {\n const { init, parent } = node;\n\n return init === null || parent.kind !== 'const' ? COMPUTED : init;\n}\n\n/** Either the expression a name stands for, or the answer for a name that stands for nothing this rule can read. */\nfunction aliasOf(node: TSESTree.Identifier, scope: TSESLint.Scope.Scope): StaticValue | TSESTree.Node {\n const [definition] = ASTUtils.findVariable(scope, node)?.defs ?? [];\n const declaration = definition?.node;\n\n if (declaration === undefined) return COMPUTED;\n\n if (IMPORT_BINDINGS.has(declaration.type)) return EXTERNAL;\n\n return declaration.type === AST_NODE_TYPES.VariableDeclarator ? constantInit(declaration) : COMPUTED;\n}\n\nfunction isAnswer(resolved: StaticValue | TSESTree.Node): resolved is StaticValue {\n return 'kind' in resolved;\n}\n\n/** `ids.order` reads a value that is written somewhere; `props.id` is built where the declaration is. */\nfunction fromMember(node: TSESTree.MemberExpression, scope: TSESLint.Scope.Scope): StaticValue {\n let root: TSESTree.Node = node;\n\n while (root.type === AST_NODE_TYPES.MemberExpression) root = root.object;\n\n if (root.type !== AST_NODE_TYPES.Identifier) return COMPUTED;\n\n const resolved = aliasOf(root, scope);\n\n return !isAnswer(resolved) || resolved.kind === 'external' ? EXTERNAL : COMPUTED;\n}\n\nfunction staticValueOf(node: TSESTree.Node, scope: TSESLint.Scope.Scope): StaticValue {\n let current = node;\n\n for (let depth = 0; depth <= MAX_DEPTH; depth += 1) {\n const expression = unwrap(current);\n const literal = literalOf(expression);\n\n if (literal !== undefined) return literal;\n\n if (expression.type === AST_NODE_TYPES.MemberExpression) return fromMember(expression, scope);\n\n if (expression.type !== AST_NODE_TYPES.Identifier) return COMPUTED;\n\n const resolved = aliasOf(expression, scope);\n\n if (isAnswer(resolved)) return resolved;\n\n current = resolved;\n }\n\n return COMPUTED;\n}\n\nexport { staticValueOf };\n"],"names":["COMPUTED","EXTERNAL","MAX_DEPTH","IMPORT_BINDINGS","AST_NODE_TYPES","unwrap","node","current","templateOf","quasi","cooked","literalOf","constantInit","init","parent","aliasOf","scope","definition","ASTUtils","declaration","isAnswer","resolved","fromMember","root","staticValueOf","depth","expression","literal"],"mappings":"wEAYA,MAAMA,EAAwB,CAAE,KAAM,UAAU,EAC1CC,EAAwB,CAAE,KAAM,UAAU,EAG1CC,EAAY,EAEZC,EAAkB,IAAI,IAAY,CACtCC,EAAe,uBACfA,EAAe,yBACfA,EAAe,eAChB,CAAA,EAED,SAASC,EAAOC,EAAmB,CACjC,IAAIC,EAAUD,EAEd,KACEC,EAAQ,OAASH,EAAe,gBAChCG,EAAQ,OAASH,EAAe,uBAChCG,EAAQ,OAASH,EAAe,qBAEhCG,EAAUA,EAAQ,WAGpB,OAAOA,CACT,CAEA,SAASC,EAAWF,EAA8B,CAChD,KAAM,CAACG,CAAK,EAAIH,EAAK,OACfI,EAASJ,EAAK,YAAY,OAAS,EAAI,KAAQG,GAAO,MAAM,QAAU,KAE5E,OAAOC,IAAW,KAAOV,EAAW,CAAE,KAAM,UAAW,MAAOU,CAAM,CACtE,CAEA,SAASC,EAAUL,EAAmB,CACpC,OAAIA,EAAK,OAASF,EAAe,QACxB,OAAOE,EAAK,OAAU,SAAW,CAAE,KAAM,UAAW,MAAOA,EAAK,KAAK,EAAKN,EAG5EM,EAAK,OAASF,EAAe,gBAAkBI,EAAWF,CAAI,EAAI,MAC3E,CAEA,SAASM,EAAaN,EAAiC,CACrD,KAAM,CAAE,KAAAO,EAAM,OAAAC,CAAM,EAAKR,EAEzB,OAAOO,IAAS,MAAQC,EAAO,OAAS,QAAUd,EAAWa,CAC/D,CAGA,SAASE,EAAQT,EAA2BU,EAA2B,CACrE,KAAM,CAACC,CAAU,EAAIC,EAAS,aAAaF,EAAOV,CAAI,GAAG,MAAQ,CAAA,EAC3Da,EAAcF,GAAY,KAEhC,OAAIE,IAAgB,OAAkBnB,EAElCG,EAAgB,IAAIgB,EAAY,IAAI,EAAUlB,EAE3CkB,EAAY,OAASf,EAAe,mBAAqBQ,EAAaO,CAAW,EAAInB,CAC9F,CAEA,SAASoB,EAASC,EAAqC,CACrD,MAAO,SAAUA,CACnB,CAGA,SAASC,EAAWhB,EAAiCU,EAA2B,CAC9E,IAAIO,EAAsBjB,EAE1B,KAAOiB,EAAK,OAASnB,EAAe,kBAAkBmB,EAAOA,EAAK,OAElE,GAAIA,EAAK,OAASnB,EAAe,WAAY,OAAOJ,EAEpD,MAAMqB,EAAWN,EAAQQ,EAAMP,CAAK,EAEpC,MAAO,CAACI,EAASC,CAAQ,GAAKA,EAAS,OAAS,WAAapB,EAAWD,CAC1E,CAEA,SAASwB,EAAclB,EAAqBU,EAA2B,CACrE,IAAIT,EAAUD,EAEd,QAASmB,EAAQ,EAAGA,GAASvB,EAAWuB,GAAS,EAAG,CAClD,MAAMC,EAAarB,EAAOE,CAAO,EAC3BoB,EAAUhB,EAAUe,CAAU,EAEpC,GAAIC,IAAY,OAAW,OAAOA,EAElC,GAAID,EAAW,OAAStB,EAAe,iBAAkB,OAAOkB,EAAWI,EAAYV,CAAK,EAE5F,GAAIU,EAAW,OAAStB,EAAe,WAAY,OAAOJ,EAE1D,MAAMqB,EAAWN,EAAQW,EAAYV,CAAK,EAE1C,GAAII,EAASC,CAAQ,EAAG,OAAOA,EAE/Bd,EAAUc,CACZ,CAEA,OAAOrB,CACT"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@opetope/lint",
3
+ "version": "0.1.0",
4
+ "engines": {
5
+ "node": ">=20.19.0"
6
+ },
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "README.ru.md",
12
+ "LICENSE",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "scripts": {
24
+ "build": "rollup --config rollup.config.cjs",
25
+ "ci:eslint": "eslint src --quiet",
26
+ "ci:oxlint-bridge": "node --test ./scripts/oxlint-bridge.test.mjs",
27
+ "ci:type": "node ../../tooling/typecheck.mjs --noEmit",
28
+ "ci:test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js && npm run ci:oxlint-bridge"
29
+ },
30
+ "dependencies": {
31
+ "@typescript-eslint/utils": "8.68.0"
32
+ },
33
+ "devDependencies": {
34
+ "@jest/globals": "30.5.0",
35
+ "@typescript-eslint/parser": "8.68.0",
36
+ "@typescript-eslint/rule-tester": "8.68.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@typescript-eslint/parser": ">=8.0.0 <9",
40
+ "eslint": ">=9.0.0 <10"
41
+ },
42
+ "sideEffects": false,
43
+ "description": "ESLint rules for authoring Opetope features and models.",
44
+ "license": "MIT",
45
+ "author": "Aleksei Berezin",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/telchardev/opetope.git",
49
+ "directory": "packages/lint"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }