@opetope/lint 0.14.0 → 0.15.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 (137) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +39 -2
  3. package/dist/ast.d.ts +14 -2
  4. package/dist/ast.js +1 -1
  5. package/dist/ast.js.map +1 -1
  6. package/dist/command-hooks.d.ts +7 -6
  7. package/dist/command-hooks.js +1 -1
  8. package/dist/command-hooks.js.map +1 -1
  9. package/dist/context-members.d.ts +4 -4
  10. package/dist/context-members.js +1 -1
  11. package/dist/context-members.js.map +1 -1
  12. package/dist/declaration-ingress.d.ts +43 -11
  13. package/dist/declaration-ingress.js +1 -1
  14. package/dist/declaration-ingress.js.map +1 -1
  15. package/dist/declarations.d.ts +1 -1
  16. package/dist/declarations.js +1 -1
  17. package/dist/declarations.js.map +1 -1
  18. package/dist/effect-declarations.d.ts +3 -2
  19. package/dist/effect-declarations.js +1 -1
  20. package/dist/effect-declarations.js.map +1 -1
  21. package/dist/entry-move-rows.d.ts +21 -0
  22. package/dist/entry-move-rows.js +2 -0
  23. package/dist/entry-move-rows.js.map +1 -0
  24. package/dist/entry-moves.d.ts +25 -0
  25. package/dist/entry-moves.js +10 -0
  26. package/dist/entry-moves.js.map +1 -0
  27. package/dist/feature-body-records.d.ts +5 -3
  28. package/dist/feature-body-records.js +1 -1
  29. package/dist/feature-body-records.js.map +1 -1
  30. package/dist/index.d.ts +65 -58
  31. package/dist/index.js +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/lazy-body-migration.d.ts +5 -5
  34. package/dist/lazy-body-migration.js +1 -1
  35. package/dist/lazy-body-migration.js.map +1 -1
  36. package/dist/model-bindings.d.ts +21 -13
  37. package/dist/model-bindings.js +1 -1
  38. package/dist/model-bindings.js.map +1 -1
  39. package/dist/resource-vocabulary.d.ts +17 -0
  40. package/dist/resource-vocabulary.js +2 -0
  41. package/dist/resource-vocabulary.js.map +1 -0
  42. package/dist/retired-vocabulary-messages.d.ts +6 -0
  43. package/dist/retired-vocabulary-messages.js +2 -0
  44. package/dist/retired-vocabulary-messages.js.map +1 -0
  45. package/dist/retired-vocabulary.d.ts +10 -8
  46. package/dist/retired-vocabulary.js +1 -1
  47. package/dist/retired-vocabulary.js.map +1 -1
  48. package/dist/rule.d.ts +94 -5
  49. package/dist/rule.js +1 -1
  50. package/dist/rule.js.map +1 -1
  51. package/dist/rules/capture-command-cleanup.d.ts +1 -4
  52. package/dist/rules/capture-command-cleanup.js +1 -1
  53. package/dist/rules/capture-command-cleanup.js.map +1 -1
  54. package/dist/rules/define-feature-property-order.d.ts +1 -4
  55. package/dist/rules/define-feature-property-order.js +1 -1
  56. package/dist/rules/define-feature-property-order.js.map +1 -1
  57. package/dist/rules/enabled-predicate.d.ts +1 -4
  58. package/dist/rules/enabled-predicate.js +1 -1
  59. package/dist/rules/enabled-predicate.js.map +1 -1
  60. package/dist/rules/id-naming.d.ts +1 -3
  61. package/dist/rules/layer-placement.d.ts +1 -3
  62. package/dist/rules/no-behavior-in-integration.d.ts +8 -0
  63. package/dist/rules/no-behavior-in-integration.js +2 -0
  64. package/dist/rules/no-behavior-in-integration.js.map +1 -0
  65. package/dist/rules/no-command-in-deps.d.ts +1 -4
  66. package/dist/rules/no-command-in-deps.js +1 -1
  67. package/dist/rules/no-command-in-deps.js.map +1 -1
  68. package/dist/rules/no-dead-abort-check.d.ts +8 -0
  69. package/dist/rules/no-dead-abort-check.js +2 -0
  70. package/dist/rules/no-dead-abort-check.js.map +1 -0
  71. package/dist/rules/no-event-without-emit.d.ts +10 -0
  72. package/dist/rules/no-event-without-emit.js +2 -0
  73. package/dist/rules/no-event-without-emit.js.map +1 -0
  74. package/dist/rules/no-inline-requires-models.d.ts +8 -0
  75. package/dist/rules/no-inline-requires-models.js +5 -0
  76. package/dist/rules/no-inline-requires-models.js.map +1 -0
  77. package/dist/rules/no-internal-imports.d.ts +1 -3
  78. package/dist/rules/no-internal-imports.js +1 -1
  79. package/dist/rules/no-internal-imports.js.map +1 -1
  80. package/dist/rules/no-redundant-const-tuple.d.ts +1 -4
  81. package/dist/rules/no-redundant-const-tuple.js +1 -1
  82. package/dist/rules/no-redundant-const-tuple.js.map +1 -1
  83. package/dist/rules/no-retired-vocabulary.d.ts +2 -4
  84. package/dist/rules/no-retired-vocabulary.js +1 -1
  85. package/dist/rules/no-retired-vocabulary.js.map +1 -1
  86. package/dist/rules/no-single-command-lane.d.ts +10 -0
  87. package/dist/rules/no-single-command-lane.js +2 -0
  88. package/dist/rules/no-single-command-lane.js.map +1 -0
  89. package/dist/rules/no-snapshot-in-update.d.ts +1 -4
  90. package/dist/rules/no-snapshot-in-update.js.map +1 -1
  91. package/dist/rules/no-snapshot-read-in-render.d.ts +2 -3
  92. package/dist/rules/no-snapshot-read-in-render.js +1 -1
  93. package/dist/rules/no-snapshot-read-in-render.js.map +1 -1
  94. package/dist/rules/no-subscribe-outside-models.d.ts +1 -3
  95. package/dist/rules/no-subscribe-outside-models.js +1 -1
  96. package/dist/rules/no-subscribe-outside-models.js.map +1 -1
  97. package/dist/rules/no-write-after-source-write.d.ts +1 -4
  98. package/dist/rules/no-write-after-source-write.js +1 -1
  99. package/dist/rules/no-write-after-source-write.js.map +1 -1
  100. package/dist/rules/prefer-derive.d.ts +7 -0
  101. package/dist/rules/prefer-derive.js +2 -0
  102. package/dist/rules/prefer-derive.js.map +1 -0
  103. package/dist/rules/prefer-effect-current.d.ts +1 -4
  104. package/dist/rules/prefer-effect-current.js +1 -1
  105. package/dist/rules/prefer-effect-current.js.map +1 -1
  106. package/dist/rules/prefer-lazy-body-record.d.ts +1 -4
  107. package/dist/rules/prefer-lazy-body-record.js +1 -1
  108. package/dist/rules/prefer-lazy-body-record.js.map +1 -1
  109. package/dist/rules/prefer-model-selection.d.ts +1 -3
  110. package/dist/rules/prefer-model-selection.js +1 -1
  111. package/dist/rules/prefer-model-selection.js.map +1 -1
  112. package/dist/rules/prefer-read.d.ts +2 -0
  113. package/dist/rules/prefer-read.js +2 -0
  114. package/dist/rules/prefer-read.js.map +1 -0
  115. package/dist/rules/prefer-when-over-enabled.d.ts +7 -0
  116. package/dist/rules/prefer-when-over-enabled.js +2 -0
  117. package/dist/rules/prefer-when-over-enabled.js.map +1 -0
  118. package/dist/rules/require-declared-models.d.ts +1 -3
  119. package/dist/rules/require-declared-models.js +1 -1
  120. package/dist/rules/require-declared-models.js.map +1 -1
  121. package/dist/rules/require-literal-id.d.ts +2 -3
  122. package/dist/rules/require-literal-id.js +1 -1
  123. package/dist/rules/require-literal-id.js.map +1 -1
  124. package/dist/rules/require-static-feature.d.ts +9 -0
  125. package/dist/rules/require-static-feature.js +2 -0
  126. package/dist/rules/require-static-feature.js.map +1 -0
  127. package/dist/rules/scope-builder-releases-subscriptions.d.ts +10 -0
  128. package/dist/rules/scope-builder-releases-subscriptions.js +2 -0
  129. package/dist/rules/scope-builder-releases-subscriptions.js.map +1 -0
  130. package/dist/snapshot-updates.d.ts +4 -3
  131. package/dist/snapshot-updates.js +1 -1
  132. package/dist/snapshot-updates.js.map +1 -1
  133. package/dist/static-value.d.ts +3 -2
  134. package/dist/static-value.js +1 -1
  135. package/dist/static-value.js.map +1 -1
  136. package/oxlintrc.json +10 -0
  137. package/package.json +13 -3
@@ -1 +1 @@
1
- {"version":3,"file":"model-bindings.js","sources":["../src/model-bindings.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, ASTUtils, TSESLint } from '@typescript-eslint/utils';\n\nimport { propertyName } from './ast';\n\ntype FunctionNode = TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression;\ntype ModelBinding = TSESLint.Scope.Variable | string;\n\nfunction isFunction(node: TSESTree.Node): node is FunctionNode {\n return (\n node.type === AST_NODE_TYPES.ArrowFunctionExpression ||\n node.type === AST_NODE_TYPES.FunctionDeclaration ||\n node.type === AST_NODE_TYPES.FunctionExpression\n );\n}\n\nfunction unwrap(node: TSESTree.Node): TSESTree.Node {\n if (\n node.type === AST_NODE_TYPES.TSAsExpression ||\n node.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n node.type === AST_NODE_TYPES.TSNonNullExpression\n )\n return unwrap(node.expression);\n\n return node;\n}\n\nfunction variableOf(source: TSESLint.SourceCode, node: TSESTree.Identifier): TSESLint.Scope.Variable | null {\n return ASTUtils.findVariable(source.getScope(node), node);\n}\n\nfunction definitionOf(source: TSESLint.SourceCode, node: TSESTree.Identifier): TSESLint.Scope.Definition | undefined {\n return variableOf(source, node)?.defs[0];\n}\n\nfunction definedValue(definition: TSESLint.Scope.Definition | undefined): TSESTree.Node | undefined {\n if (definition?.type === TSESLint.Scope.DefinitionType.FunctionName) return definition.node;\n if (definition?.type === TSESLint.Scope.DefinitionType.Variable) return definition.node.init ?? undefined;\n return undefined;\n}\n\n/** Local values only: an imported component or a parameter has no inspectable implementation here. */\nfunction localValue(source: TSESLint.SourceCode, node: TSESTree.Node, seen = new Set<TSESTree.Node>()): TSESTree.Node {\n const value = unwrap(node);\n\n if (value.type !== AST_NODE_TYPES.Identifier || seen.has(value)) return value;\n\n seen.add(value);\n const defined = definedValue(definitionOf(source, value));\n\n return defined === undefined ? value : localValue(source, defined, seen);\n}\n\nfunction importName(node: TSESTree.ImportSpecifier): string {\n return node.imported.type === AST_NODE_TYPES.Identifier ? node.imported.name : node.imported.value;\n}\n\n/**\n * A relative re-export is useful inside an Opetope package; React is the module it names and nothing else. That\n * allowance widens what a rule reports, so a check that uses the import to stay *silent* asks for `exact` instead:\n * there a host's own `./my-context` must not pass for ours.\n */\nfunction importsFrom(node: TSESTree.Node, module: string, exact: boolean): boolean {\n if (node.type !== AST_NODE_TYPES.ImportDeclaration) return false;\n\n if (node.source.value === module) return true;\n\n return !exact && module.startsWith('@opetope/') && node.source.value.startsWith('.');\n}\n\n/** The name an import carries in the module that exports it, past the local alias a file gave it. */\nfunction importedApiName(\n source: TSESLint.SourceCode,\n node: TSESTree.Node,\n module: string,\n exact: boolean,\n): string | undefined {\n if (node.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n const definition = definitionOf(source, node);\n\n if (\n definition?.type !== TSESLint.Scope.DefinitionType.ImportBinding ||\n !importsFrom(definition.parent, module, exact)\n )\n return undefined;\n\n return definition.node.type === AST_NODE_TYPES.ImportSpecifier ? importName(definition.node) : undefined;\n}\n\n/** `React.useEffect`: the name a member expression reads and the plain name it reads it from. */\nfunction namespaceMember(node: TSESTree.Node): { name: string; object: TSESTree.Identifier } | undefined {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return undefined;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.object.type !== AST_NODE_TYPES.Identifier)\n return undefined;\n\n return { name: node.property.name, object: node.object };\n}\n\nfunction memberObject(node: TSESTree.Node, name: string): TSESTree.Identifier | undefined {\n const member = namespaceMember(node);\n\n return member?.name === name ? member.object : undefined;\n}\n\n/**\n * The module taken whole and read through. React's own hooks arrive in the default binding as often as in a\n * namespace one, so `react` accepts both; an `@opetope/*` entry has no default export, and its resolution is\n * unchanged by that allowance.\n */\nfunction isWholeModule(\n source: TSESLint.SourceCode,\n object: TSESTree.Identifier,\n module: string,\n exact: boolean,\n): boolean {\n const definition = definitionOf(source, object);\n\n if (\n definition?.type !== TSESLint.Scope.DefinitionType.ImportBinding ||\n !importsFrom(definition.parent, module, exact)\n )\n return false;\n\n if (definition.node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) return true;\n\n return module === 'react' && definition.node.type === AST_NODE_TYPES.ImportDefaultSpecifier;\n}\n\n/** The name an API carries where it is exported, whichever way this file took it in. */\nfunction apiName(source: TSESLint.SourceCode, node: TSESTree.Node, module: string, exact = false): string | undefined {\n const value = localValue(source, node);\n const member = namespaceMember(value);\n\n if (member === undefined) return importedApiName(source, value, module, exact);\n\n return isWholeModule(source, member.object, module, exact) ? member.name : undefined;\n}\n\nfunction isApi(source: TSESLint.SourceCode, node: TSESTree.Node, module: string, name: string, exact = false): boolean {\n return apiName(source, node, module, exact) === name;\n}\n\nfunction importedModel(definition: TSESLint.Scope.Definition | undefined): string | undefined {\n if (\n definition?.type !== TSESLint.Scope.DefinitionType.ImportBinding ||\n definition.node.type !== AST_NODE_TYPES.ImportSpecifier ||\n definition.parent.type !== AST_NODE_TYPES.ImportDeclaration\n )\n return undefined;\n return `import:${definition.parent.source.value}:${importName(definition.node)}`;\n}\n\n/** Identity, not spelling: equal names in different lexical scopes are different model declarations. */\nfunction directModelBinding(source: TSESLint.SourceCode, identity: TSESTree.Identifier): ModelBinding {\n return importedModel(definitionOf(source, identity)) ?? variableOf(source, identity) ?? `unbound:${identity.name}`;\n}\n\nfunction modelBinding(\n source: TSESLint.SourceCode,\n node: TSESTree.Node,\n seen = new Set<TSESTree.Node>(),\n): ModelBinding | undefined {\n const identity = unwrap(node);\n if (identity.type !== AST_NODE_TYPES.Identifier) return undefined;\n const definition = definitionOf(source, identity);\n const defined = definedValue(definition);\n if (defined?.type === AST_NODE_TYPES.Identifier && !seen.has(identity)) {\n seen.add(identity);\n return modelBinding(source, defined, seen);\n }\n return directModelBinding(source, identity);\n}\n\nfunction isFeatureCall(source: TSESLint.SourceCode, node: TSESTree.Node, exact: boolean): boolean {\n if (node.type !== AST_NODE_TYPES.CallExpression) return false;\n\n let callee: TSESTree.Node = node.callee;\n\n if (\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === 'body'\n )\n callee = callee.object;\n\n return isApi(source, callee, '@opetope/runtime', 'defineFeature', exact);\n}\n\n/** A section written as a callback right where `defineFeature` takes it: `provides: (…) => …`, `own: (…) => …`. */\nfunction isFeatureSection(source: TSESLint.SourceCode, fn: FunctionNode, section: string, exact = false): boolean {\n const property = fn.parent;\n\n return (\n property.type === AST_NODE_TYPES.Property &&\n propertyName(property) === section &&\n property.parent.type === AST_NODE_TYPES.ObjectExpression &&\n isFeatureCall(source, property.parent.parent, exact)\n );\n}\n\nfunction sectionParameter(\n source: TSESLint.SourceCode,\n node: TSESTree.Identifier,\n section: string,\n): FunctionNode | undefined {\n const definition = definitionOf(source, node);\n if (\n definition?.type !== TSESLint.Scope.DefinitionType.Parameter ||\n !isFunction(definition.node) ||\n !isFeatureSection(source, definition.node, section)\n )\n return undefined;\n return definition.node;\n}\n\nfunction slotParameter(source: TSESLint.SourceCode, node: TSESTree.Identifier): boolean {\n const pattern = sectionParameter(source, node, 'provides')?.params[0];\n if (pattern?.type !== AST_NODE_TYPES.ObjectPattern) return false;\n return pattern.properties.some(\n property =>\n property.type === AST_NODE_TYPES.Property &&\n propertyName(property) === 'slot' &&\n property.value.type === AST_NODE_TYPES.Identifier &&\n property.value.name === node.name,\n );\n}\n\nfunction namedSlot(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const object = memberObject(node, 'slot');\n if (object === undefined) return false;\n const fn = sectionParameter(source, object, 'provides');\n const parameter = fn?.params[0];\n return parameter?.type === AST_NODE_TYPES.Identifier && parameter.name === object.name;\n}\n\nfunction isSlot(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return value.type === AST_NODE_TYPES.Identifier ? slotParameter(source, value) : namedSlot(source, value);\n}\n\nfunction enclosingFunction(source: TSESLint.SourceCode, node: TSESTree.Node): FunctionNode | undefined {\n return source.getAncestors(node).reverse().find(isFunction);\n}\n\nexport type { FunctionNode, ModelBinding };\nexport {\n apiName,\n definitionOf,\n enclosingFunction,\n isApi,\n isFeatureSection,\n isFunction,\n isSlot,\n localValue,\n modelBinding,\n unwrap,\n variableOf,\n};\n"],"names":["isFunction","node","AST_NODE_TYPES","unwrap","variableOf","source","ASTUtils","definitionOf","definedValue","definition","TSESLint","localValue","seen","value","defined","importName","importsFrom","module","exact","importedApiName","namespaceMember","memberObject","name","member","isWholeModule","object","apiName","isApi","importedModel","directModelBinding","identity","modelBinding","isFeatureCall","callee","isFeatureSection","fn","section","property","propertyName","sectionParameter","slotParameter","pattern","namedSlot","parameter","isSlot","enclosingFunction"],"mappings":"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"}
1
+ {"version":3,"file":"model-bindings.js","sources":["../src/model-bindings.ts"],"sourcesContent":["import { DefinitionType } from '@typescript-eslint/scope-manager';\nimport { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { propertyName, within } from './ast';\nimport type { Definition, Scope, SourceCode, Variable } from './rule';\nimport { findVariable } from './rule';\n\ntype FunctionNode = TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression;\ntype ModelBinding = Variable | string;\n\nfunction isFunction(node: TSESTree.Node): node is FunctionNode {\n return (\n node.type === AST_NODE_TYPES.ArrowFunctionExpression ||\n node.type === AST_NODE_TYPES.FunctionDeclaration ||\n node.type === AST_NODE_TYPES.FunctionExpression\n );\n}\n\nfunction unwrap(node: TSESTree.Node): TSESTree.Node {\n if (\n node.type === AST_NODE_TYPES.TSAsExpression ||\n node.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n node.type === AST_NODE_TYPES.TSNonNullExpression\n )\n return unwrap(node.expression);\n\n return node;\n}\n\nfunction variableOf(source: SourceCode, node: TSESTree.Identifier): Variable | null {\n return findVariable(source.getScope(node), node);\n}\n\nfunction definitionOf(source: SourceCode, node: TSESTree.Identifier): Definition | undefined {\n return variableOf(source, node)?.defs[0];\n}\n\nfunction definedValue(definition: Definition | undefined): TSESTree.Node | undefined {\n if (definition?.type === DefinitionType.FunctionName) return definition.node;\n if (definition?.type === DefinitionType.Variable) return definition.node.init ?? undefined;\n return undefined;\n}\n\n/** An initializer proves provenance only while no other write can replace its binding. */\nfunction hasWritesBeyondDefinition(variable: Variable, definition: Definition): boolean {\n const initial = definition.type === DefinitionType.Variable ? definition.node.id : undefined;\n return variable.references.some(reference => reference.isWrite() && reference.identifier !== initial);\n}\n\nfunction stableDefinedValue(source: SourceCode, node: TSESTree.Identifier): TSESTree.Node | undefined {\n const variable = variableOf(source, node);\n if (variable?.defs.length !== 1) return undefined;\n const definition = variable.defs[0]!;\n return hasWritesBeyondDefinition(variable, definition) ? undefined : definedValue(definition);\n}\n\n/** Local values only: an imported component or a parameter has no inspectable implementation here. */\nfunction localValue(source: SourceCode, node: TSESTree.Node, seen = new Set<TSESTree.Node>()): TSESTree.Node {\n const value = unwrap(node);\n\n if (value.type !== AST_NODE_TYPES.Identifier || seen.has(value)) return value;\n\n seen.add(value);\n const defined = stableDefinedValue(source, value);\n\n return defined === undefined ? value : localValue(source, defined, seen);\n}\n\nfunction importName(node: TSESTree.ImportSpecifier): string {\n return node.imported.type === AST_NODE_TYPES.Identifier ? node.imported.name : node.imported.value;\n}\n\n/**\n * A relative re-export is useful inside an Opetope package; React is the module it names and nothing else. That\n * allowance widens what a rule reports, so a check that uses the import to stay *silent* asks for `exact` instead:\n * there a host's own `./my-context` must not pass for ours.\n */\nfunction importsFrom(node: TSESTree.Node, module: string, exact: boolean): boolean {\n if (node.type !== AST_NODE_TYPES.ImportDeclaration) return false;\n\n if (node.source.value === module) return true;\n\n return !exact && module.startsWith('@opetope/') && node.source.value.startsWith('.');\n}\n\n/** The name an import carries in the module that exports it, past the local alias a file gave it. */\nfunction importedApiName(source: SourceCode, node: TSESTree.Node, module: string, exact: boolean): string | undefined {\n if (node.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n const definition = definitionOf(source, node);\n\n if (definition?.type !== DefinitionType.ImportBinding || !importsFrom(definition.parent, module, exact))\n return undefined;\n\n return definition.node.type === AST_NODE_TYPES.ImportSpecifier ? importName(definition.node) : undefined;\n}\n\n/** `React.useEffect`: the name a member expression reads and the plain name it reads it from. */\nfunction namespaceMember(node: TSESTree.Node): { name: string; object: TSESTree.Identifier } | undefined {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return undefined;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.object.type !== AST_NODE_TYPES.Identifier)\n return undefined;\n\n return { name: node.property.name, object: node.object };\n}\n\nfunction memberObject(node: TSESTree.Node, name: string): TSESTree.Identifier | undefined {\n const member = namespaceMember(node);\n\n return member?.name === name ? member.object : undefined;\n}\n\n/**\n * The module taken whole and read through. React's own hooks arrive in the default binding as often as in a\n * namespace one, so `react` accepts both; an `@opetope/*` entry has no default export, and its resolution is\n * unchanged by that allowance.\n */\nfunction isWholeModule(source: SourceCode, object: TSESTree.Identifier, module: string, exact: boolean): boolean {\n const definition = definitionOf(source, object);\n\n if (definition?.type !== DefinitionType.ImportBinding || !importsFrom(definition.parent, module, exact)) return false;\n\n if (definition.node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) return true;\n\n return module === 'react' && definition.node.type === AST_NODE_TYPES.ImportDefaultSpecifier;\n}\n\n/** The name an API carries where it is exported, whichever way this file took it in. */\nfunction apiName(source: SourceCode, node: TSESTree.Node, module: string, exact = false): string | undefined {\n const value = localValue(source, node);\n const member = namespaceMember(value);\n\n if (member === undefined) return importedApiName(source, value, module, exact);\n\n return isWholeModule(source, member.object, module, exact) ? member.name : undefined;\n}\n\nfunction isApi(source: SourceCode, node: TSESTree.Node, module: string, name: string, exact = false): boolean {\n return apiName(source, node, module, exact) === name;\n}\n\nfunction importedModel(definition: Definition | undefined): string | undefined {\n if (\n definition?.type !== DefinitionType.ImportBinding ||\n definition.node.type !== AST_NODE_TYPES.ImportSpecifier ||\n definition.parent.type !== AST_NODE_TYPES.ImportDeclaration\n )\n return undefined;\n return `import:${definition.parent.source.value}:${importName(definition.node)}`;\n}\n\n/** Identity, not spelling: equal names in different lexical scopes are different model declarations. */\nfunction directModelBinding(source: SourceCode, identity: TSESTree.Identifier): ModelBinding {\n return importedModel(definitionOf(source, identity)) ?? variableOf(source, identity) ?? `unbound:${identity.name}`;\n}\n\nfunction modelBinding(\n source: SourceCode,\n node: TSESTree.Node,\n seen = new Set<TSESTree.Node>(),\n): ModelBinding | undefined {\n const identity = unwrap(node);\n if (identity.type !== AST_NODE_TYPES.Identifier) return undefined;\n const definition = definitionOf(source, identity);\n const defined = definedValue(definition);\n if (defined?.type === AST_NODE_TYPES.Identifier && !seen.has(identity)) {\n seen.add(identity);\n return modelBinding(source, defined, seen);\n }\n return directModelBinding(source, identity);\n}\n\nfunction isFeatureCall(source: SourceCode, node: TSESTree.Node, exact: boolean): boolean {\n if (node.type !== AST_NODE_TYPES.CallExpression) return false;\n\n let callee: TSESTree.Node = node.callee;\n\n if (\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === 'body'\n )\n callee = callee.object;\n\n return isApi(source, callee, '@opetope/runtime', 'defineFeature', exact);\n}\n\n/** A section written as a callback right where `defineFeature` takes it: `provides: (…) => …`, `own: (…) => …`. */\nfunction isFeatureSection(source: SourceCode, fn: FunctionNode, section: string, exact = false): boolean {\n const property = fn.parent;\n\n return (\n property.type === AST_NODE_TYPES.Property &&\n propertyName(property) === section &&\n property.parent.type === AST_NODE_TYPES.ObjectExpression &&\n isFeatureCall(source, property.parent.parent, exact)\n );\n}\n\nfunction sectionParameter(source: SourceCode, node: TSESTree.Identifier, section: string): FunctionNode | undefined {\n const definition = definitionOf(source, node);\n if (\n definition?.type !== DefinitionType.Parameter ||\n !isFunction(definition.node) ||\n !isFeatureSection(source, definition.node, section)\n )\n return undefined;\n return definition.node;\n}\n\nfunction slotParameter(source: SourceCode, node: TSESTree.Identifier): boolean {\n const pattern = sectionParameter(source, node, 'provides')?.params[0];\n if (pattern?.type !== AST_NODE_TYPES.ObjectPattern) return false;\n return pattern.properties.some(\n property =>\n property.type === AST_NODE_TYPES.Property &&\n propertyName(property) === 'slot' &&\n property.value.type === AST_NODE_TYPES.Identifier &&\n property.value.name === node.name,\n );\n}\n\nfunction namedSlot(source: SourceCode, node: TSESTree.Node): boolean {\n const object = memberObject(node, 'slot');\n if (object === undefined) return false;\n const fn = sectionParameter(source, object, 'provides');\n const parameter = fn?.params[0];\n return parameter?.type === AST_NODE_TYPES.Identifier && parameter.name === object.name;\n}\n\nfunction isSlot(source: SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return value.type === AST_NODE_TYPES.Identifier ? slotParameter(source, value) : namedSlot(source, value);\n}\n\n/** Every scope under this one, itself included: where the references of an expression are recorded. */\nfunction scopesUnder(scope: Scope, found: Scope[] = []): Scope[] {\n found.push(scope);\n\n for (const child of scope.childScopes) scopesUnder(child, found);\n\n return found;\n}\n\n/**\n * The identifiers `node` reads as references, in the scope of `holder` and every scope under it: a key of a record, a\n * member after `.` and a name a pattern binds are not among them.\n */\nfunction referencesWithin(source: SourceCode, holder: TSESTree.Node, node: TSESTree.Node): TSESTree.Identifier[] {\n const found: TSESTree.Identifier[] = [];\n\n for (const scope of scopesUnder(source.getScope(holder))) {\n for (const { identifier } of scope.references) {\n if (identifier.type === AST_NODE_TYPES.Identifier && within(identifier, node)) found.push(identifier);\n }\n }\n\n return found;\n}\n\nfunction enclosingFunction(source: SourceCode, node: TSESTree.Node): FunctionNode | undefined {\n return source.getAncestors(node).reverse().find(isFunction);\n}\n\nexport type { FunctionNode, ModelBinding };\nexport {\n apiName,\n definitionOf,\n enclosingFunction,\n hasWritesBeyondDefinition,\n importName,\n isApi,\n isFeatureSection,\n isFunction,\n isSlot,\n localValue,\n modelBinding,\n referencesWithin,\n unwrap,\n variableOf,\n};\n"],"names":["isFunction","node","AST_NODE_TYPES","unwrap","variableOf","source","findVariable","definitionOf","definedValue","definition","DefinitionType","hasWritesBeyondDefinition","variable","initial","reference","stableDefinedValue","localValue","seen","value","defined","importName","importsFrom","module","exact","importedApiName","namespaceMember","memberObject","name","member","isWholeModule","object","apiName","isApi","importedModel","directModelBinding","identity","modelBinding","isFeatureCall","callee","isFeatureSection","fn","section","property","propertyName","sectionParameter","slotParameter","pattern","namedSlot","parameter","isSlot","scopesUnder","scope","found","child","referencesWithin","holder","identifier","within","enclosingFunction"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { TSESTree } from '@typescript-eslint/types';
2
+ import type { ReportFixFunction, SourceCode } from './rule.js';
3
+ type ResourceMessageId = 'resourceExecutionHold' | 'resourceFallback' | 'resourceHold' | 'resourceLifetime' | 'resourceLifetimeType' | 'resourcePartition';
4
+ type Report = (node: TSESTree.Node, messageId: ResourceMessageId, fix?: ReportFixFunction) => void;
5
+ /**
6
+ * The wave's Resource spellings are fixed only after their declaring context or exact public import is established.
7
+ * An options alias can be reported but not rewritten: another user of that record may still mean its old property.
8
+ * D514: a factory that only has the shape of a model's — the last argument of a `model` or `openModel` this file does
9
+ * not prove — is named and never rewritten, and so is `acquire(view)` on the context of an execution, whose member
10
+ * an author often destructures under the old name.
11
+ */
12
+ declare function createResourceVocabulary(source: SourceCode, report: Report): {
13
+ visitCall: (call: TSESTree.CallExpression) => void;
14
+ visitType: (node: TSESTree.TSTypeReference) => void;
15
+ };
16
+ export { createResourceVocabulary };
17
+ export type { ResourceMessageId };
@@ -0,0 +1,2 @@
1
+ import{DefinitionType as C}from"@typescript-eslint/scope-manager";import{AST_NODE_TYPES as o}from"@typescript-eslint/types";import{findProperty as l}from"./ast.js";import{methodPath as M,isModelContextFunction as k,isModelFactoryShape as D,isExecutionCallback as H}from"./declaration-ingress.js";import{apiName as x,isApi as _,variableOf as $,hasWritesBeyondDefinition as q,definitionOf as F,localValue as m,unwrap as j}from"./model-bindings.js";const B=new Map([["Resource",2],["OwnedResource",2],["ResourcePagination",1],["PaginatedResource",3],["OwnedPaginatedResource",3],["OwnedListResource",4]]),U=new Set(["resource.load","resource.live","resource.list"]);function b(r){return r.type===o.MemberExpression?r.property:r}function y(r,u,f){const s=r.getText(u)[0];return s==="'"||s==='"'?`${s}${f}${s}`:JSON.stringify(f)}function V(r,u,f){if(u.shorthand)return u.value.type!==o.Identifier?void 0:s=>s.replaceText(u,`${f}: ${r.getText(u.value)}`);const d=u.key.type===o.Literal?y(r,u.key,f):f;return s=>s.replaceText(u.key,d)}function W(r,u){const f=new Set,d=(e,t,n)=>{f.has(e)||(f.add(e),u(e,t,n))},s=(e,t)=>{const n=e.arguments[t];if(n===void 0||n.type===o.SpreadElement)return;const i=m(r,n);return i.type===o.ObjectExpression?{direct:n===i,record:i}:void 0},T=(e,t,n,i,p)=>{const c=l(e,t);c!==void 0&&d(c.key,i,p?V(r,c,n):void 0)},v=(e,t)=>{const n=l(e,"lifetime");if(n===void 0)return;const i=m(r,n.value);if(i.type!==o.Literal||i.value!=="owner")return;const p=j(n.value),c=t&&p===i?P=>P.replaceText(i,y(r,i,"model")):void 0;d(n.value,"resourceLifetime",c)},R=(e,t)=>{const n=l(e,"identity");if(n===void 0)return;const i=m(r,n.value);i.type===o.ObjectExpression&&T(i,"scope","partition","resourcePartition",t&&n.value===i)},S=e=>{let t=e;for(;t.type===o.MemberExpression;)t=t.object;if(t.type!==o.Identifier)return!1;const n=$(r,t);if(n?.defs.length!==1)return!1;const i=n.defs[0];return i!==void 0&&!q(n,i)},E=e=>e.arguments.length===1&&e.arguments[0]?.type!==o.SpreadElement,h=(e,t)=>{if(!E(e))return;const n=e.callee;t&&n.type===o.MemberExpression&&!n.computed?d(n.property,"resourceHold",i=>i.replaceText(n.property,"hold")):d(b(n),"resourceHold")},O=(e,t)=>{const n=s(e,0);n!==void 0&&(v(n.record,t&&n.direct),R(n.record,t&&n.direct))},g=(e,t,n)=>{if(t==="acquire"){h(e,n);return}if(t==="resource.view"){const i=s(e,2);i!==void 0&&T(i.record,"initial","fallback","resourceFallback",n&&i.direct);return}U.has(t)&&O(e,n)},a=e=>{if(e===void 0)return;const t=e.type===o.TSUnionType?e.types:[e];for(const n of t)n.type!==o.TSLiteralType||n.literal.type!==o.Literal||n.literal.value!=="owner"||d(n.literal,"resourceLifetimeType",i=>i.replaceText(n.literal,y(r,n.literal,"model")))},w=e=>{const t=s(e,0);t!==void 0&&v(t.record,t.direct),a(e.typeArguments?.params[2])},I=(e,t,n)=>t==="acquire"&&E(e)&&H(r,n),L=e=>{if(_(r,e.callee,"@opetope/runtime/testing","testResource",!0)){w(e);return}const t=M(r,e.callee);t===void 0||!S(e.callee)||(k(r,t.fn)?g(e,t.method,!0):I(e,t.method,t.fn)?d(b(e.callee),"resourceExecutionHold"):D(t.fn)&&g(e,t.method,!1))},A=e=>{if(e.left.type!==o.Identifier)return;const t=F(r,e.left);if(t?.type===C.ImportBinding&&t.node.type===o.ImportNamespaceSpecifier&&t.parent.type===o.ImportDeclaration)return t.parent.source.value==="@opetope/core"?e.right.name:void 0},N=e=>e.type===o.Identifier?x(r,e,"@opetope/core",!0):e.type===o.TSQualifiedName?A(e):void 0;return{visitCall:L,visitType:e=>{const{typeName:t}=e,n=N(t),i=n===void 0?void 0:B.get(n);if(i!==void 0){a(e.typeArguments?.params[i]);return}t.type===o.Identifier&&x(r,t,"@opetope/runtime/testing",!0)==="TestResource"&&a(e.typeArguments?.params[2])}}}export{W as createResourceVocabulary};
2
+ //# sourceMappingURL=resource-vocabulary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-vocabulary.js","sources":["../src/resource-vocabulary.ts"],"sourcesContent":["import { DefinitionType } from '@typescript-eslint/scope-manager';\nimport { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { findProperty } from './ast';\nimport { isExecutionCallback, isModelContextFunction, isModelFactoryShape, methodPath } from './declaration-ingress';\nimport {\n apiName,\n definitionOf,\n hasWritesBeyondDefinition,\n isApi,\n localValue,\n unwrap,\n variableOf,\n} from './model-bindings';\nimport type { FunctionNode } from './model-bindings';\nimport type { ReportFixFunction, SourceCode } from './rule';\n\ntype ResourceMessageId =\n | 'resourceExecutionHold'\n | 'resourceFallback'\n | 'resourceHold'\n | 'resourceLifetime'\n | 'resourceLifetimeType'\n | 'resourcePartition';\ntype Report = (node: TSESTree.Node, messageId: ResourceMessageId, fix?: ReportFixFunction) => void;\n\n/** The lifetime parameter's position belongs to the named public type, not to an arbitrary `owner` string. */\nconst CORE_LIFETIME_TYPES = new Map([\n ['Resource', 2],\n ['OwnedResource', 2],\n ['ResourcePagination', 1],\n ['PaginatedResource', 3],\n ['OwnedPaginatedResource', 3],\n ['OwnedListResource', 4],\n]);\nconst RESOURCE_DECLARATIONS = new Set(['resource.load', 'resource.live', 'resource.list']);\n\n/** The word a callee ends in: `acquire` of `execution.acquire`, or the plain name a destructured member is called by. */\nfunction calleeWord(callee: TSESTree.Node): TSESTree.Node {\n return callee.type === AST_NODE_TYPES.MemberExpression ? callee.property : callee;\n}\n\n/** Keep the author's quote style when the literal itself is the whole safe edit. */\nfunction renamedLiteral(source: SourceCode, node: TSESTree.Node, word: string): string {\n const written = source.getText(node);\n const quote = written[0];\n return quote === \"'\" || quote === '\"' ? `${quote}${word}${quote}` : JSON.stringify(word);\n}\n\n/** Shorthand `{ scope }` must become `{ partition: scope }`, leaving the source binding unchanged. */\nfunction renameKey(source: SourceCode, property: TSESTree.Property, word: string): ReportFixFunction | undefined {\n if (property.shorthand) {\n if (property.value.type !== AST_NODE_TYPES.Identifier) return undefined;\n return fixer => fixer.replaceText(property, `${word}: ${source.getText(property.value)}`);\n }\n\n const replacement = property.key.type === AST_NODE_TYPES.Literal ? renamedLiteral(source, property.key, word) : word;\n return fixer => fixer.replaceText(property.key, replacement);\n}\n\n/**\n * The wave's Resource spellings are fixed only after their declaring context or exact public import is established.\n * An options alias can be reported but not rewritten: another user of that record may still mean its old property.\n * D514: a factory that only has the shape of a model's — the last argument of a `model` or `openModel` this file does\n * not prove — is named and never rewritten, and so is `acquire(view)` on the context of an execution, whose member\n * an author often destructures under the old name.\n */\nfunction createResourceVocabulary(\n source: SourceCode,\n report: Report,\n): {\n visitCall: (call: TSESTree.CallExpression) => void;\n visitType: (node: TSESTree.TSTypeReference) => void;\n} {\n const reported = new Set<TSESTree.Node>();\n const once: Report = (node, messageId, fix) => {\n if (reported.has(node)) return;\n reported.add(node);\n report(node, messageId, fix);\n };\n\n const recordAt = (call: TSESTree.CallExpression, position: number) => {\n const argument = call.arguments[position];\n if (argument === undefined || argument.type === AST_NODE_TYPES.SpreadElement) return undefined;\n const record = localValue(source, argument);\n return record.type === AST_NODE_TYPES.ObjectExpression ? { direct: argument === record, record } : undefined;\n };\n\n const renamedProperty = (\n record: TSESTree.ObjectExpression,\n oldName: string,\n newName: string,\n messageId: ResourceMessageId,\n direct: boolean,\n ) => {\n const property = findProperty(record, oldName);\n if (property === undefined) return;\n once(property.key, messageId, direct ? renameKey(source, property, newName) : undefined);\n };\n\n const lifetime = (record: TSESTree.ObjectExpression, direct: boolean) => {\n const property = findProperty(record, 'lifetime');\n if (property === undefined) return;\n const value = localValue(source, property.value);\n if (value.type !== AST_NODE_TYPES.Literal || value.value !== 'owner') return;\n const written = unwrap(property.value);\n const fix =\n direct && written === value\n ? (fixer: Parameters<ReportFixFunction>[0]) => fixer.replaceText(value, renamedLiteral(source, value, 'model'))\n : undefined;\n once(property.value, 'resourceLifetime', fix);\n };\n\n const identity = (record: TSESTree.ObjectExpression, direct: boolean) => {\n const property = findProperty(record, 'identity');\n if (property === undefined) return;\n const nested = localValue(source, property.value);\n if (nested.type !== AST_NODE_TYPES.ObjectExpression) return;\n renamedProperty(nested, 'scope', 'partition', 'resourcePartition', direct && property.value === nested);\n };\n\n /** A declaring parameter whose binding was replaced no longer proves the receiver's provenance. */\n const stableReceiver = (callee: TSESTree.Node): boolean => {\n let root = callee;\n while (root.type === AST_NODE_TYPES.MemberExpression) root = root.object;\n if (root.type !== AST_NODE_TYPES.Identifier) return false;\n const variable = variableOf(source, root);\n if (variable?.defs.length !== 1) return false;\n const definition = variable.defs[0];\n return definition !== undefined && !hasWritesBeyondDefinition(variable, definition);\n };\n\n const isHoldCall = (call: TSESTree.CallExpression): boolean =>\n call.arguments.length === 1 && call.arguments[0]?.type !== AST_NODE_TYPES.SpreadElement;\n\n const visitHold = (call: TSESTree.CallExpression, proven: boolean): void => {\n if (!isHoldCall(call)) return;\n const callee = call.callee;\n if (proven && callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed)\n once(callee.property, 'resourceHold', fixer => fixer.replaceText(callee.property, 'hold'));\n else once(calleeWord(callee), 'resourceHold');\n };\n\n const visitDeclaration = (call: TSESTree.CallExpression, proven: boolean): void => {\n const options = recordAt(call, 0);\n if (options === undefined) return;\n lifetime(options.record, proven && options.direct);\n identity(options.record, proven && options.direct);\n };\n\n /** `proven`: the context is a model's by its import or its section; otherwise a word is named, never rewritten. */\n const visitModelCall = (call: TSESTree.CallExpression, method: string, proven: boolean): void => {\n if (method === 'acquire') {\n visitHold(call, proven);\n return;\n }\n if (method === 'resource.view') {\n const options = recordAt(call, 2);\n if (options !== undefined)\n renamedProperty(options.record, 'initial', 'fallback', 'resourceFallback', proven && options.direct);\n return;\n }\n if (RESOURCE_DECLARATIONS.has(method)) visitDeclaration(call, proven);\n };\n\n const visitLifetimeArgument = (argument: TSESTree.TypeNode | undefined): void => {\n if (argument === undefined) return;\n const members = argument.type === AST_NODE_TYPES.TSUnionType ? argument.types : [argument];\n for (const member of members) {\n if (\n member.type !== AST_NODE_TYPES.TSLiteralType ||\n member.literal.type !== AST_NODE_TYPES.Literal ||\n member.literal.value !== 'owner'\n )\n continue;\n once(member.literal, 'resourceLifetimeType', fixer =>\n fixer.replaceText(member.literal, renamedLiteral(source, member.literal, 'model')),\n );\n }\n };\n\n const visitTestResource = (call: TSESTree.CallExpression): void => {\n const options = recordAt(call, 0);\n if (options !== undefined) lifetime(options.record, options.direct);\n visitLifetimeArgument(call.typeArguments?.params[2]);\n };\n\n /** A hold an execution context still spells `acquire`: named, since its destructured name may be renamed too. */\n const isExecutionAcquire = (call: TSESTree.CallExpression, method: string, fn: FunctionNode): boolean =>\n method === 'acquire' && isHoldCall(call) && isExecutionCallback(source, fn);\n\n const visitCall = (call: TSESTree.CallExpression): void => {\n if (isApi(source, call.callee, '@opetope/runtime/testing', 'testResource', true)) {\n visitTestResource(call);\n return;\n }\n\n const named = methodPath(source, call.callee);\n if (named === undefined || !stableReceiver(call.callee)) return;\n if (isModelContextFunction(source, named.fn)) visitModelCall(call, named.method, true);\n else if (isExecutionAcquire(call, named.method, named.fn)) once(calleeWord(call.callee), 'resourceExecutionHold');\n else if (isModelFactoryShape(named.fn)) visitModelCall(call, named.method, false);\n };\n\n const namespaceCoreName = (name: TSESTree.TSQualifiedName): string | undefined => {\n if (name.left.type !== AST_NODE_TYPES.Identifier) return undefined;\n const definition = definitionOf(source, name.left);\n if (definition?.type !== DefinitionType.ImportBinding) return undefined;\n if (definition.node.type !== AST_NODE_TYPES.ImportNamespaceSpecifier) return undefined;\n if (definition.parent.type !== AST_NODE_TYPES.ImportDeclaration) return undefined;\n return definition.parent.source.value === '@opetope/core' ? name.right.name : undefined;\n };\n\n const coreTypeName = (name: TSESTree.TSTypeReference['typeName']): string | undefined => {\n if (name.type === AST_NODE_TYPES.Identifier) return apiName(source, name, '@opetope/core', true);\n return name.type === AST_NODE_TYPES.TSQualifiedName ? namespaceCoreName(name) : undefined;\n };\n\n const visitType = (node: TSESTree.TSTypeReference): void => {\n const { typeName } = node;\n const coreName = coreTypeName(typeName);\n const corePosition = coreName === undefined ? undefined : CORE_LIFETIME_TYPES.get(coreName);\n if (corePosition !== undefined) {\n visitLifetimeArgument(node.typeArguments?.params[corePosition]);\n return;\n }\n if (typeName.type !== AST_NODE_TYPES.Identifier) return;\n if (apiName(source, typeName, '@opetope/runtime/testing', true) === 'TestResource')\n visitLifetimeArgument(node.typeArguments?.params[2]);\n };\n\n return { visitCall, visitType };\n}\n\nexport { createResourceVocabulary };\nexport type { ResourceMessageId };\n"],"names":["CORE_LIFETIME_TYPES","RESOURCE_DECLARATIONS","calleeWord","callee","AST_NODE_TYPES","renamedLiteral","source","node","word","quote","renameKey","property","fixer","replacement","createResourceVocabulary","report","reported","once","messageId","fix","recordAt","call","position","argument","record","localValue","renamedProperty","oldName","newName","direct","findProperty","lifetime","value","written","unwrap","identity","nested","stableReceiver","root","variable","variableOf","definition","hasWritesBeyondDefinition","isHoldCall","visitHold","proven","visitDeclaration","options","visitModelCall","method","visitLifetimeArgument","members","member","visitTestResource","isExecutionAcquire","fn","isExecutionCallback","visitCall","isApi","named","methodPath","isModelContextFunction","isModelFactoryShape","namespaceCoreName","name","definitionOf","DefinitionType","coreTypeName","apiName","typeName","coreName","corePosition"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** The words `no-retired-vocabulary` names, and what it says about each: the migration a retired word asks for. */
2
+ import type { ResourceMessageId } from './resource-vocabulary.js';
3
+ type MessageIds = ResourceMessageId | 'commandLane' | 'commandLatest' | 'commandInputArgs' | 'commandName' | 'commandOnce' | 'commandOutcomeStatus' | 'commandPolicy' | 'commandQueueBy' | 'conditionFrom' | 'contextCall' | 'contextCalls' | 'contributionWhen' | 'deliveryKey' | 'deliveryKind' | 'deliveryReject' | 'effectOnDispose' | 'effectSkipInitial' | 'effectWhen' | 'entryMoved' | 'entryRenamed' | 'eventBackpressure' | 'externalPublish' | 'fromExternal' | 'featureOwnMember' | 'linkSourcePending' | 'reactFeatureDemand' | 'reactUseSelector' | 'navigatorPolicy' | 'paginateNextCursor' | 'resourceActivity' | 'resourceDisposer' | 'resourceInvalidate' | 'resourcePagination' | 'runtimeInternalEntry' | 'scopeKeyed' | 'scopeWhileWhen' | 'stateActivity' | 'surfaceView' | 'useCommands';
4
+ declare const RETIRED_MESSAGES: Readonly<Record<MessageIds, string>>;
5
+ export { RETIRED_MESSAGES };
6
+ export type { MessageIds };
@@ -0,0 +1,2 @@
1
+ const e={commandLane:'`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: "latest" }` where `policy: "latest"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',commandLatest:'`concurrency: "latest"` is now `concurrency: { pending: "latest" }`, and `{ lane, pending: "latest" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',commandInputArgs:"`{{word}}` is no longer published: a wrapper of `runCommand` forwards `...args: {{replacement}}<Input, Keys>` from `@opetope/runtime/testing`, and a wrapper of `CommandHook.run` forwards `...args: Parameters<CommandHook<Input, Output>['run']>` (D510, D517).",commandName:"`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).",commandOnce:"`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).",commandOutcomeStatus:'`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged \u2014 `{ kind: "ok", value }`, `{ kind: "failed", error }`, `{ kind: "cancelled", reason }` (D411).',commandPolicy:'`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: "latest" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value \u2014 `{ by }`, `{ by, lane }` or `{ lane, pending: "latest" }` \u2014 so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',commandQueueBy:"`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).",conditionFrom:"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads \u2014 `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).",contextCall:"`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` \u2014 and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => \u2026`), a constant names it (`const refill: Command<Refill, void> = ctx.command(\u2026)`), and the annotation of that context \u2014 `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature \u2014 is what is left; a feature has the last two only. `command<Deposit>(\u2026)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).",contextCalls:'`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, "name")` answers one command and `select(source, ["a", "b"])` a record of them (D388).',contributionWhen:"`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the declared `Condition`s of `defineFeature({ when })` (D378, D411, D517).",deliveryKey:'Inside a delivery record `key` is now `by`: `{ pending: "latest", by }` keeps one waiting place per key instead of one for the whole node (D374).',deliveryKind:'Inside a delivery record `kind` is now `pending`: `kind: "latest"` is `pending: "latest"`, and `kind: "queue"` is `pending: "all"` \u2014 the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',deliveryReject:'`overflow: "reject"` is now `overflow: "drop"`: one word at both nodes for \xABthe new one does not enter and `emit` answers `false`\xBB. `"flush-oldest"` is unchanged and still the default of a keyed delivery (D369, D411).',effectOnDispose:"`onDispose` is now `finalize`: an effect has two releases \u2014 the disposer a run returns, and the one the node itself gets once \u2014 and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).",effectSkipInitial:"`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite \u2014 which is why this rule names it and leaves the rewrite to you (D379).",entryMoved:"At `{{from}}`, {{names}} changed: {{verb}} {{them}} from {{to}}. {{reason}} {{how}} ({{decision}}).",entryRenamed:"At `{{from}}`, {{names}} changed spelling. {{reason}} {{how}} ({{decision}}).",effectWhen:"`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => \u2026`; a predicate whose whole job is to refuse the first run \u2014 `(_current, previous) => previous !== undefined` \u2014 is `initial: false`. This rule does not guess between them. `defineFeature.when` still names declared Conditions; at a contribution it is now `enabled` (D379, D411, D517).",eventBackpressure:'`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: "latest"` for one waiting place that the newest payload takes, or the record \u2014 `{ pending, by }`, `{ pending, capacity, overflow }` \u2014 for the rest (D374).',featureOwnMember:"`{{word}}` is removed from Feature own. Declare this behavior with `own.model(Model, deps, (ctx, deps) => ({ ... }))`. In that model, {{replacement}} (D501).",reactFeatureDemand:"`{{word}}` is removed from `@opetope/react/integration` until a public producer of feature demand exists. A contribution boundary remains `ContributionBoundary` (D500).",reactUseSelector:"`useSelector` is removed. In a model selection use `read(source, project)`; for a standalone source create `derive(source, project, { equals })` once in a model, at module scope or with `useMemo` by key, then pass it to `useReadable`. Move the old `equals` option to `derive` (D500).",externalPublish:"`publish` on a proven Kit ExternalSource is now `set`, the same write verb as testReadable (D502).",fromExternal:"`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).",linkSourcePending:"`pending` on a proven Navigation LinkSource is now `intent`: it retains the latest value until its generation is acknowledged. Kit Inbox.pending is unchanged (D512).",paginateNextCursor:"`nextCursor` is now `cursor`: `resource.paginate(resource, { cursor, load, merge })`. TypeScript reads the members of one record in the order they are written, and the selector of the cursor has to come before `load`, so it is named to sort first \u2014 the alphabetical order `sort-keys` keeps is the one that infers with no annotation (D486).",navigatorPolicy:"`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data \u2014 a word when it has no parameters, a record when it has them (D411).",resourceActivity:"`ResourceActivity` is retired at both entries that spelled it. From `@opetope/devtools` it is `ResourceNodeActivity`, of `@opetope/devtools/protocol` now (D411, D495). From `@opetope/core` it is gone: a state answers what failed and what is working in two fields of its own, so read `state.failure` and `state.pending` instead of a union that flattened them (D462).",resourceDisposer:"`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell \u2014 `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).",resourceInvalidate:"`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means \xABmark stale, keep the data\xBB, so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).",resourceExecutionHold:"`acquire(view)` of a command, effect or event context is now `hold(view)`: it holds the view until that execution ends. Rename it by hand, the destructured name included; public `Resource.acquire()` keeps its name (D514).",resourceFallback:"`initial` in the options of `resource.view` is now `fallback`: it is published whenever the selected Resource has no data, not just at first materialization. `--fix` renames it on a proven model context; a shared options record or an unproven context is renamed by hand (D514).",resourceHold:"`ModelContext.acquire(view)` is now `hold(view)`: the context holds Resource demand for its model; public `Resource.acquire()` still creates a lease and keeps its own name. A destructured member or an unproven context is renamed by hand (D514).",resourceLifetime:"Resource lifetime `'owner'` is now `'model'`: the model owns the Resource while it is active. `--fix` renames the literal on a proven model context; a shared options record or an unproven context is renamed by hand (D514).",resourceLifetimeType:"The Resource lifetime type argument `'owner'` is now `'model'`; the `Owned*` type names remain (D514).",resourcePartition:"`identity.scope` is now `identity.partition`: changing it clears the whole Resource cache; partitions are not kept side by side. `--fix` renames it on a proven model context; a shared options record or an unproven context is renamed by hand (D514).",resourcePagination:"`pagination` is no longer a field of a declaration: apply it to the Resource the declaration answers, `const pages = ctx.resource.paginate(ctx.resource.load({ \u2026 }), { cursor, load, merge })`, in the model that declares it. The one `load` of the pages takes the cursor, `nextCursor` is `cursor`, and a feature that pages holds its Resource in a model it owns (D486).",runtimeInternalEntry:"`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).",scopeKeyed:"`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required \u2014 so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).",scopeWhileWhen:"`when` in `scope.while(source, open, { when })` is now `filter`: it selects source values while one child remains open. `defineFeature.when` still names declared Conditions (D517).",stateActivity:"No `ResourceState` carries `activity` any more: the union said what a channel was doing and what had failed in one word. Read `state.failure` \u2014 `undefined` is \xABnothing failed\xBB, and otherwise `channel` names the channel that died and `error` carries its failure \u2014 and `state.pending` for the channel with work in flight. Both are declared on every member, so neither needs a narrowing by `kind` first (D462).",surfaceView:"`view` on a proven Navigation Surface is now `regions`: it returns the stable record of region targets for one declared screen (D512).",useCommands:"`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388)."};export{e as RETIRED_MESSAGES};
2
+ //# sourceMappingURL=retired-vocabulary-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retired-vocabulary-messages.js","sources":["../src/retired-vocabulary-messages.ts"],"sourcesContent":["/** The words `no-retired-vocabulary` names, and what it says about each: the migration a retired word asks for. */\n\nimport type { ResourceMessageId } from './resource-vocabulary';\n\ntype MessageIds =\n | ResourceMessageId\n | 'commandLane'\n | 'commandLatest'\n | 'commandInputArgs'\n | 'commandName'\n | 'commandOnce'\n | 'commandOutcomeStatus'\n | 'commandPolicy'\n | 'commandQueueBy'\n | 'conditionFrom'\n | 'contextCall'\n | 'contextCalls'\n | 'contributionWhen'\n | 'deliveryKey'\n | 'deliveryKind'\n | 'deliveryReject'\n | 'effectOnDispose'\n | 'effectSkipInitial'\n | 'effectWhen'\n | 'entryMoved'\n | 'entryRenamed'\n | 'eventBackpressure'\n | 'externalPublish'\n | 'fromExternal'\n | 'featureOwnMember'\n | 'linkSourcePending'\n | 'reactFeatureDemand'\n | 'reactUseSelector'\n | 'navigatorPolicy'\n | 'paginateNextCursor'\n | 'resourceActivity'\n | 'resourceDisposer'\n | 'resourceInvalidate'\n | 'resourcePagination'\n | 'runtimeInternalEntry'\n | 'scopeKeyed'\n | 'scopeWhileWhen'\n | 'stateActivity'\n | 'surfaceView'\n | 'useCommands';\n\nconst RETIRED_MESSAGES: Readonly<Record<MessageIds, string>> = {\n commandLane:\n '`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: \"latest\" }` where `policy: \"latest\"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',\n commandLatest:\n '`concurrency: \"latest\"` is now `concurrency: { pending: \"latest\" }`, and `{ lane, pending: \"latest\" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',\n commandInputArgs:\n \"`{{word}}` is no longer published: a wrapper of `runCommand` forwards `...args: {{replacement}}<Input, Keys>` from `@opetope/runtime/testing`, and a wrapper of `CommandHook.run` forwards `...args: Parameters<CommandHook<Input, Output>['run']>` (D510, D517).\",\n commandName:\n '`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).',\n commandOnce:\n '`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).',\n commandOutcomeStatus:\n '`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged — `{ kind: \"ok\", value }`, `{ kind: \"failed\", error }`, `{ kind: \"cancelled\", reason }` (D411).',\n commandPolicy:\n '`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: \"latest\" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value — `{ by }`, `{ by, lane }` or `{ lane, pending: \"latest\" }` — so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',\n commandQueueBy:\n '`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).',\n conditionFrom:\n \"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads — `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).\",\n contextCall:\n '`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` — and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => …`), a constant names it (`const refill: Command<Refill, void> = ctx.command(…)`), and the annotation of that context — `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature — is what is left; a feature has the last two only. `command<Deposit>(…)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).',\n contextCalls:\n '`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, \"name\")` answers one command and `select(source, [\"a\", \"b\"])` a record of them (D388).',\n contributionWhen:\n '`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the declared `Condition`s of `defineFeature({ when })` (D378, D411, D517).',\n deliveryKey:\n 'Inside a delivery record `key` is now `by`: `{ pending: \"latest\", by }` keeps one waiting place per key instead of one for the whole node (D374).',\n deliveryKind:\n 'Inside a delivery record `kind` is now `pending`: `kind: \"latest\"` is `pending: \"latest\"`, and `kind: \"queue\"` is `pending: \"all\"` — the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',\n deliveryReject:\n '`overflow: \"reject\"` is now `overflow: \"drop\"`: one word at both nodes for «the new one does not enter and `emit` answers `false`». `\"flush-oldest\"` is unchanged and still the default of a keyed delivery (D369, D411).',\n effectOnDispose:\n '`onDispose` is now `finalize`: an effect has two releases — the disposer a run returns, and the one the node itself gets once — and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).',\n effectSkipInitial:\n '`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite — which is why this rule names it and leaves the rewrite to you (D379).',\n entryMoved: 'At `{{from}}`, {{names}} changed: {{verb}} {{them}} from {{to}}. {{reason}} {{how}} ({{decision}}).',\n entryRenamed: 'At `{{from}}`, {{names}} changed spelling. {{reason}} {{how}} ({{decision}}).',\n effectWhen:\n '`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => …`; a predicate whose whole job is to refuse the first run — `(_current, previous) => previous !== undefined` — is `initial: false`. This rule does not guess between them. `defineFeature.when` still names declared Conditions; at a contribution it is now `enabled` (D379, D411, D517).',\n eventBackpressure:\n '`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: \"latest\"` for one waiting place that the newest payload takes, or the record — `{ pending, by }`, `{ pending, capacity, overflow }` — for the rest (D374).',\n featureOwnMember:\n '`{{word}}` is removed from Feature own. Declare this behavior with `own.model(Model, deps, (ctx, deps) => ({ ... }))`. In that model, {{replacement}} (D501).',\n reactFeatureDemand:\n '`{{word}}` is removed from `@opetope/react/integration` until a public producer of feature demand exists. A contribution boundary remains `ContributionBoundary` (D500).',\n reactUseSelector:\n '`useSelector` is removed. In a model selection use `read(source, project)`; for a standalone source create `derive(source, project, { equals })` once in a model, at module scope or with `useMemo` by key, then pass it to `useReadable`. Move the old `equals` option to `derive` (D500).',\n externalPublish: '`publish` on a proven Kit ExternalSource is now `set`, the same write verb as testReadable (D502).',\n fromExternal:\n '`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).',\n linkSourcePending:\n '`pending` on a proven Navigation LinkSource is now `intent`: it retains the latest value until its generation is acknowledged. Kit Inbox.pending is unchanged (D512).',\n paginateNextCursor:\n '`nextCursor` is now `cursor`: `resource.paginate(resource, { cursor, load, merge })`. TypeScript reads the members of one record in the order they are written, and the selector of the cursor has to come before `load`, so it is named to sort first — the alphabetical order `sort-keys` keeps is the one that infers with no annotation (D486).',\n navigatorPolicy:\n '`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data — a word when it has no parameters, a record when it has them (D411).',\n resourceActivity:\n '`ResourceActivity` is retired at both entries that spelled it. From `@opetope/devtools` it is `ResourceNodeActivity`, of `@opetope/devtools/protocol` now (D411, D495). From `@opetope/core` it is gone: a state answers what failed and what is working in two fields of its own, so read `state.failure` and `state.pending` instead of a union that flattened them (D462).',\n resourceDisposer:\n '`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell — `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).',\n resourceInvalidate:\n '`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means «mark stale, keep the data», so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).',\n resourceExecutionHold:\n '`acquire(view)` of a command, effect or event context is now `hold(view)`: it holds the view until that execution ends. Rename it by hand, the destructured name included; public `Resource.acquire()` keeps its name (D514).',\n resourceFallback:\n '`initial` in the options of `resource.view` is now `fallback`: it is published whenever the selected Resource has no data, not just at first materialization. `--fix` renames it on a proven model context; a shared options record or an unproven context is renamed by hand (D514).',\n resourceHold:\n '`ModelContext.acquire(view)` is now `hold(view)`: the context holds Resource demand for its model; public `Resource.acquire()` still creates a lease and keeps its own name. A destructured member or an unproven context is renamed by hand (D514).',\n resourceLifetime:\n \"Resource lifetime `'owner'` is now `'model'`: the model owns the Resource while it is active. `--fix` renames the literal on a proven model context; a shared options record or an unproven context is renamed by hand (D514).\",\n resourceLifetimeType:\n \"The Resource lifetime type argument `'owner'` is now `'model'`; the `Owned*` type names remain (D514).\",\n resourcePartition:\n '`identity.scope` is now `identity.partition`: changing it clears the whole Resource cache; partitions are not kept side by side. `--fix` renames it on a proven model context; a shared options record or an unproven context is renamed by hand (D514).',\n resourcePagination:\n '`pagination` is no longer a field of a declaration: apply it to the Resource the declaration answers, `const pages = ctx.resource.paginate(ctx.resource.load({ … }), { cursor, load, merge })`, in the model that declares it. The one `load` of the pages takes the cursor, `nextCursor` is `cursor`, and a feature that pages holds its Resource in a model it owns (D486).',\n runtimeInternalEntry:\n '`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).',\n scopeKeyed:\n '`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required — so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).',\n scopeWhileWhen:\n '`when` in `scope.while(source, open, { when })` is now `filter`: it selects source values while one child remains open. `defineFeature.when` still names declared Conditions (D517).',\n stateActivity:\n 'No `ResourceState` carries `activity` any more: the union said what a channel was doing and what had failed in one word. Read `state.failure` — `undefined` is «nothing failed», and otherwise `channel` names the channel that died and `error` carries its failure — and `state.pending` for the channel with work in flight. Both are declared on every member, so neither needs a narrowing by `kind` first (D462).',\n surfaceView:\n '`view` on a proven Navigation Surface is now `regions`: it returns the stable record of region targets for one declared screen (D512).',\n useCommands:\n '`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388).',\n};\n\nexport { RETIRED_MESSAGES };\nexport type { MessageIds };\n"],"names":["RETIRED_MESSAGES"],"mappings":"AA8CA,MAAMA,EAAyD,CAC7D,YACE,6TACF,cACE,0XACF,iBACE,oQACF,YACE,kPACF,YACE,oOACF,qBACE,8WACF,cACE,saACF,eACE,6JACF,cACE,yTACF,YACE,04BACF,aACE,8RACF,iBACE,+TACF,YACE,oJACF,aACE,8RACF,eACE,kOACF,gBACE,kQACF,kBACE,wTACF,WAAY,sGACZ,aAAc,gFACd,WACE,odACF,kBACE,wQACF,iBACE,gKACF,mBACE,2KACF,iBACE,8RACF,gBAAiB,qGACjB,aACE,6MACF,kBACE,wKACF,mBACE,2VACF,gBACE,kPACF,iBACE,gXACF,iBACE,+OACF,mBACE,yUACF,sBACE,gOACF,iBACE,wRACF,aACE,uPACF,iBACE,iOACF,qBACE,yGACF,kBACE,2PACF,mBACE,qXACF,qBACE,uNACF,WACE,4TACF,eACE,uLACF,cACE,0aACF,YACE,yIACF,YACE"}
@@ -1,31 +1,33 @@
1
- import type { TSESTree } from '@typescript-eslint/utils';
2
- import { TSESLint } from '@typescript-eslint/utils';
1
+ import type { TSESTree } from '@typescript-eslint/types';
2
+ import type { SourceCode } from './rule.js';
3
3
  /**
4
4
  * A value this module proved is a Resource of this library: the `resource.load` or `resource.live` that declared it,
5
5
  * read past the name it was given. The shape decides first and the resolution second, so a call that cannot be a
6
6
  * declaration never pays for the context it would have been declared on (D323).
7
7
  */
8
- declare function isResourceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean;
8
+ declare function isResourceValue(source: SourceCode, node: TSESTree.Node): boolean;
9
9
  /**
10
10
  * `useResource(book)`: the record of hooks, under whatever local name this file gave the import. The module is
11
11
  * `@opetope/react` exactly — a host's own `./hooks` must not pass for ours, which is what `exact` asks for (D309).
12
12
  */
13
- declare function isResourceHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean;
13
+ declare function isResourceHook(source: SourceCode, node: TSESTree.Node): boolean;
14
14
  /**
15
15
  * D411, D415: the surface `defineSurface` answers, under whatever local name this file gave the import. The module
16
16
  * is `@opetope/navigation/react` exactly, for the reason `useResource` is asked exactly (D309): `policy` is a word
17
17
  * a host writes about its own objects all day, so the rule asks the symbol and never the spelling.
18
18
  */
19
- declare function isSurfaceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean;
19
+ declare function isSurfaceValue(source: SourceCode, node: TSESTree.Node): boolean;
20
+ /** A test link source built by the Navigation fixture, rather than an unrelated object with `pending`. */
21
+ declare function isLinkSourceValue(source: SourceCode, node: TSESTree.Node): boolean;
20
22
  /**
21
23
  * D462: `activity` is gone from every member of `ResourceState`, so a read of it is retired wherever the expression
22
24
  * is one of ours — there is no branch left in which the field survives. D375 had retired it on `loading` alone, so
23
25
  * the rule had to prove which branch a read stood in; that machinery is gone with the half-retirement it served.
24
26
  */
25
- declare function isStateActivity(source: TSESLint.SourceCode, node: TSESTree.MemberExpression): boolean;
27
+ declare function isStateActivity(source: SourceCode, node: TSESTree.MemberExpression): boolean;
26
28
  /**
27
29
  * D411: the settled answer a consumer keeps — `.outcome` read on a `useCommand`, or the name it was destructured
28
30
  * into. `status` is a common word, so the rule asks the symbol: nothing but this object is reported.
29
31
  */
30
- declare function isCommandOutcome(source: TSESLint.SourceCode, node: TSESTree.Node): boolean;
31
- export { isCommandOutcome, isResourceHook, isResourceValue, isStateActivity, isSurfaceValue };
32
+ declare function isCommandOutcome(source: SourceCode, node: TSESTree.Node): boolean;
33
+ export { isCommandOutcome, isLinkSourceValue, isResourceHook, isResourceValue, isStateActivity, isSurfaceValue };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as i,TSESLint as y}from"@typescript-eslint/utils";import{propertyName as S}from"./ast.js";import{methodPath as E,isContextFunction as C}from"./declaration-ingress.js";import{localValue as o,isApi as a,unwrap as u,definitionOf as b}from"./model-bindings.js";const x=new Set(["resource.live","resource.load"]);function c(e,t){const n=o(e,t);if(n.type!==i.CallExpression)return!1;const r=E(e,n.callee);return r!==void 0&&x.has(r.method)&&C(e,r.fn)}function f(e,t){const n=o(e,t);return n.type===i.CallExpression&&a(e,n.callee,"@opetope/react","useResource",!0)}function l(e,t){const n=o(e,t);return n.type===i.CallExpression&&a(e,n.callee,"@opetope/react","useCommand",!0)}function v(e,t){const n=o(e,t);return n.type===i.CallExpression&&a(e,n.callee,"@opetope/navigation/react","defineSurface",!0)}function m(e,t){if(t.type!==i.MemberExpression||t.computed||t.property.type!==i.Identifier||t.property.name!=="state")return!1;const n=u(t.object);return c(e,n)||f(e,n)}function R(e,t){return t.type!==i.MemberExpression||t.computed||t.property.type!==i.Identifier||t.property.name!=="getSnapshot"?!1:m(e,u(t.object))}function d(e,t,n){const r=b(e,t);if(r?.type!==y.Scope.DefinitionType.Variable)return;const{init:s}=r.node,p=r.name.parent;if(!(s===null||p.type!==i.Property)&&!(S(p)!==n||p.parent!==r.node.id))return s}function h(e,t){if(t.type===i.Identifier){const r=d(e,t,"state");if(r!==void 0)return f(e,r)}const n=o(e,t);return n.type===i.CallExpression?R(e,n.callee):m(e,n)}function I(e,t){return h(e,t.object)}function j(e,t){if(t.type===i.Identifier){const r=d(e,t,"outcome");if(r!==void 0)return l(e,r)}const n=o(e,t);return n.type!==i.MemberExpression||n.computed||n.property.type!==i.Identifier||n.property.name!=="outcome"?!1:l(e,u(n.object))}export{j as isCommandOutcome,f as isResourceHook,c as isResourceValue,I as isStateActivity,v as isSurfaceValue};
1
+ import{DefinitionType as d}from"@typescript-eslint/scope-manager";import{AST_NODE_TYPES as r}from"@typescript-eslint/types";import{propertyName as S}from"./ast.js";import{methodPath as E,isContextFunction as v}from"./declaration-ingress.js";import{localValue as o,isApi as a,unwrap as u,definitionOf as C}from"./model-bindings.js";const x=new Set(["resource.live","resource.load"]);function l(e,t){const n=o(e,t);if(n.type!==r.CallExpression)return!1;const i=E(e,n.callee);return i!==void 0&&x.has(i.method)&&v(e,i.fn)}function f(e,t){const n=o(e,t);return n.type===r.CallExpression&&a(e,n.callee,"@opetope/react","useResource",!0)}function c(e,t){const n=o(e,t);return n.type===r.CallExpression&&a(e,n.callee,"@opetope/react","useCommand",!0)}function b(e,t){const n=o(e,t);return n.type===r.CallExpression&&a(e,n.callee,"@opetope/navigation/react","defineSurface",!0)}function k(e,t){const n=o(e,t);return n.type===r.CallExpression&&(a(e,n.callee,"@opetope/navigation/testing","testLinkSource",!0)||a(e,n.callee,"@opetope/navigation/testing","fakeLinkSource",!0))}function m(e,t){if(t.type!==r.MemberExpression||t.computed||t.property.type!==r.Identifier||t.property.name!=="state")return!1;const n=u(t.object);return l(e,n)||f(e,n)}function R(e,t){return t.type!==r.MemberExpression||t.computed||t.property.type!==r.Identifier||t.property.name!=="getSnapshot"?!1:m(e,u(t.object))}function y(e,t,n){const i=C(e,t);if(i?.type!==d.Variable)return;const{init:s}=i.node,p=i.name.parent;if(!(s===null||p.type!==r.Property)&&!(S(p)!==n||p.parent!==i.node.id))return s}function g(e,t){if(t.type===r.Identifier){const i=y(e,t,"state");if(i!==void 0)return f(e,i)}const n=o(e,t);return n.type===r.CallExpression?R(e,n.callee):m(e,n)}function h(e,t){return g(e,t.object)}function I(e,t){if(t.type===r.Identifier){const i=y(e,t,"outcome");if(i!==void 0)return c(e,i)}const n=o(e,t);return n.type!==r.MemberExpression||n.computed||n.property.type!==r.Identifier||n.property.name!=="outcome"?!1:c(e,u(n.object))}export{I as isCommandOutcome,k as isLinkSourceValue,f as isResourceHook,l as isResourceValue,h as isStateActivity,b as isSurfaceValue};
2
2
  //# sourceMappingURL=retired-vocabulary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"retired-vocabulary.js","sources":["../src/retired-vocabulary.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils';\n\nimport { propertyName } from './ast';\nimport { isContextFunction, methodPath } from './declaration-ingress';\nimport { definitionOf, isApi, localValue, unwrap } from './model-bindings';\n\n/** D349: the two factories of the Resource family. Both answer the object whose verbs a migration renames. */\nconst RESOURCE_FACTORIES = new Set(['resource.live', 'resource.load']);\n/**\n * A value this module proved is a Resource of this library: the `resource.load` or `resource.live` that declared it,\n * read past the name it was given. The shape decides first and the resolution second, so a call that cannot be a\n * declaration never pays for the context it would have been declared on (D323).\n */\nfunction isResourceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.CallExpression) return false;\n\n const named = methodPath(source, value.callee);\n\n return named !== undefined && RESOURCE_FACTORIES.has(named.method) && isContextFunction(source, named.fn);\n}\n\n/**\n * `useResource(book)`: the record of hooks, under whatever local name this file gave the import. The module is\n * `@opetope/react` exactly — a host's own `./hooks` must not pass for ours, which is what `exact` asks for (D309).\n */\nfunction isResourceHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useResource', true)\n );\n}\n\n/**\n * D411: `useCommand(save)` — the one consumer of a command, under whatever local name this file gave the import.\n * The module is `@opetope/react` exactly, for the reason `useResource` is asked exactly (D309).\n */\nfunction isCommandHook(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useCommand', true)\n );\n}\n\n/**\n * D411, D415: the surface `defineSurface` answers, under whatever local name this file gave the import. The module\n * is `@opetope/navigation/react` exactly, for the reason `useResource` is asked exactly (D309): `policy` is a word\n * a host writes about its own objects all day, so the rule asks the symbol and never the spelling.\n */\nfunction isSurfaceValue(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression &&\n isApi(source, value.callee, '@opetope/navigation/react', 'defineSurface', true)\n );\n}\n\n/** `.state` read on a Resource of this library or on the record `useResource` answers. */\nfunction isStateMember(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'state') return false;\n\n const object = unwrap(node.object);\n\n return isResourceValue(source, object) || isResourceHook(source, object);\n}\n\n/** `book.state.getSnapshot()`: the state as a model reads it. */\nfunction isSnapshotRead(source: TSESLint.SourceCode, callee: TSESTree.Node): boolean {\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return false;\n\n return isStateMember(source, unwrap(callee.object));\n}\n\n/**\n * `const { state } = useResource(book)`: the value a name was destructured out of, under the key it was taken by.\n * `localValue` answers the whole initializer for such a name, which is the record and not the member, so the key is\n * read here instead — the way a context member is read off the pattern that bound it.\n */\nfunction takenFrom(source: TSESLint.SourceCode, node: TSESTree.Identifier, key: string): TSESTree.Node | undefined {\n const definition = definitionOf(source, node);\n\n if (definition?.type !== TSESLint.Scope.DefinitionType.Variable) return undefined;\n\n const { init } = definition.node;\n const property = definition.name.parent;\n\n if (init === null || property.type !== AST_NODE_TYPES.Property) return undefined;\n\n if (propertyName(property) !== key || property.parent !== definition.node.id) return undefined;\n\n return init;\n}\n\n/** The `ResourceState` a name holds: the member itself, one value read out of it, or the key it was taken from. */\nfunction isResourceState(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'state');\n\n if (taken !== undefined) return isResourceHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n return value.type === AST_NODE_TYPES.CallExpression\n ? isSnapshotRead(source, value.callee)\n : isStateMember(source, value);\n}\n\n/**\n * D462: `activity` is gone from every member of `ResourceState`, so a read of it is retired wherever the expression\n * is one of ours — there is no branch left in which the field survives. D375 had retired it on `loading` alone, so\n * the rule had to prove which branch a read stood in; that machinery is gone with the half-retirement it served.\n */\nfunction isStateActivity(source: TSESLint.SourceCode, node: TSESTree.MemberExpression): boolean {\n return isResourceState(source, node.object);\n}\n\n/**\n * D411: the settled answer a consumer keeps — `.outcome` read on a `useCommand`, or the name it was destructured\n * into. `status` is a common word, so the rule asks the symbol: nothing but this object is reported.\n */\nfunction isCommandOutcome(source: TSESLint.SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'outcome');\n\n if (taken !== undefined) return isCommandHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.MemberExpression || value.computed) return false;\n\n if (value.property.type !== AST_NODE_TYPES.Identifier || value.property.name !== 'outcome') return false;\n\n return isCommandHook(source, unwrap(value.object));\n}\n\nexport { isCommandOutcome, isResourceHook, isResourceValue, isStateActivity, isSurfaceValue };\n"],"names":["RESOURCE_FACTORIES","isResourceValue","source","node","value","localValue","AST_NODE_TYPES","named","methodPath","isContextFunction","isResourceHook","isApi","isCommandHook","isSurfaceValue","isStateMember","object","unwrap","isSnapshotRead","callee","takenFrom","key","definition","definitionOf","TSESLint","init","property","propertyName","isResourceState","taken","isStateActivity","isCommandOutcome"],"mappings":"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"}
1
+ {"version":3,"file":"retired-vocabulary.js","sources":["../src/retired-vocabulary.ts"],"sourcesContent":["import { DefinitionType } from '@typescript-eslint/scope-manager';\nimport { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { propertyName } from './ast';\nimport { isContextFunction, methodPath } from './declaration-ingress';\nimport { definitionOf, isApi, localValue, unwrap } from './model-bindings';\nimport type { SourceCode } from './rule';\n\n/** D349: the two factories of the Resource family. Both answer the object whose verbs a migration renames. */\nconst RESOURCE_FACTORIES = new Set(['resource.live', 'resource.load']);\n/**\n * A value this module proved is a Resource of this library: the `resource.load` or `resource.live` that declared it,\n * read past the name it was given. The shape decides first and the resolution second, so a call that cannot be a\n * declaration never pays for the context it would have been declared on (D323).\n */\nfunction isResourceValue(source: SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.CallExpression) return false;\n\n const named = methodPath(source, value.callee);\n\n return named !== undefined && RESOURCE_FACTORIES.has(named.method) && isContextFunction(source, named.fn);\n}\n\n/**\n * `useResource(book)`: the record of hooks, under whatever local name this file gave the import. The module is\n * `@opetope/react` exactly — a host's own `./hooks` must not pass for ours, which is what `exact` asks for (D309).\n */\nfunction isResourceHook(source: SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useResource', true)\n );\n}\n\n/**\n * D411: `useCommand(save)` — the one consumer of a command, under whatever local name this file gave the import.\n * The module is `@opetope/react` exactly, for the reason `useResource` is asked exactly (D309).\n */\nfunction isCommandHook(source: SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression && isApi(source, value.callee, '@opetope/react', 'useCommand', true)\n );\n}\n\n/**\n * D411, D415: the surface `defineSurface` answers, under whatever local name this file gave the import. The module\n * is `@opetope/navigation/react` exactly, for the reason `useResource` is asked exactly (D309): `policy` is a word\n * a host writes about its own objects all day, so the rule asks the symbol and never the spelling.\n */\nfunction isSurfaceValue(source: SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression &&\n isApi(source, value.callee, '@opetope/navigation/react', 'defineSurface', true)\n );\n}\n\n/** A test link source built by the Navigation fixture, rather than an unrelated object with `pending`. */\nfunction isLinkSourceValue(source: SourceCode, node: TSESTree.Node): boolean {\n const value = localValue(source, node);\n\n return (\n value.type === AST_NODE_TYPES.CallExpression &&\n (isApi(source, value.callee, '@opetope/navigation/testing', 'testLinkSource', true) ||\n isApi(source, value.callee, '@opetope/navigation/testing', 'fakeLinkSource', true))\n );\n}\n\n/** `.state` read on a Resource of this library or on the record `useResource` answers. */\nfunction isStateMember(source: SourceCode, node: TSESTree.Node): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'state') return false;\n\n const object = unwrap(node.object);\n\n return isResourceValue(source, object) || isResourceHook(source, object);\n}\n\n/** `book.state.getSnapshot()`: the state as a model reads it. */\nfunction isSnapshotRead(source: SourceCode, callee: TSESTree.Node): boolean {\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return false;\n\n return isStateMember(source, unwrap(callee.object));\n}\n\n/**\n * `const { state } = useResource(book)`: the value a name was destructured out of, under the key it was taken by.\n * `localValue` answers the whole initializer for such a name, which is the record and not the member, so the key is\n * read here instead — the way a context member is read off the pattern that bound it.\n */\nfunction takenFrom(source: SourceCode, node: TSESTree.Identifier, key: string): TSESTree.Node | undefined {\n const definition = definitionOf(source, node);\n\n if (definition?.type !== DefinitionType.Variable) return undefined;\n\n const { init } = definition.node;\n const property = definition.name.parent;\n\n if (init === null || property.type !== AST_NODE_TYPES.Property) return undefined;\n\n if (propertyName(property) !== key || property.parent !== definition.node.id) return undefined;\n\n return init;\n}\n\n/** The `ResourceState` a name holds: the member itself, one value read out of it, or the key it was taken from. */\nfunction isResourceState(source: SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'state');\n\n if (taken !== undefined) return isResourceHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n return value.type === AST_NODE_TYPES.CallExpression\n ? isSnapshotRead(source, value.callee)\n : isStateMember(source, value);\n}\n\n/**\n * D462: `activity` is gone from every member of `ResourceState`, so a read of it is retired wherever the expression\n * is one of ours — there is no branch left in which the field survives. D375 had retired it on `loading` alone, so\n * the rule had to prove which branch a read stood in; that machinery is gone with the half-retirement it served.\n */\nfunction isStateActivity(source: SourceCode, node: TSESTree.MemberExpression): boolean {\n return isResourceState(source, node.object);\n}\n\n/**\n * D411: the settled answer a consumer keeps — `.outcome` read on a `useCommand`, or the name it was destructured\n * into. `status` is a common word, so the rule asks the symbol: nothing but this object is reported.\n */\nfunction isCommandOutcome(source: SourceCode, node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) {\n const taken = takenFrom(source, node, 'outcome');\n\n if (taken !== undefined) return isCommandHook(source, taken);\n }\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.MemberExpression || value.computed) return false;\n\n if (value.property.type !== AST_NODE_TYPES.Identifier || value.property.name !== 'outcome') return false;\n\n return isCommandHook(source, unwrap(value.object));\n}\n\nexport { isCommandOutcome, isLinkSourceValue, isResourceHook, isResourceValue, isStateActivity, isSurfaceValue };\n"],"names":["RESOURCE_FACTORIES","isResourceValue","source","node","value","localValue","AST_NODE_TYPES","named","methodPath","isContextFunction","isResourceHook","isApi","isCommandHook","isSurfaceValue","isLinkSourceValue","isStateMember","object","unwrap","isSnapshotRead","callee","takenFrom","key","definition","definitionOf","DefinitionType","init","property","propertyName","isResourceState","taken","isStateActivity","isCommandOutcome"],"mappings":"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"}
package/dist/rule.d.ts CHANGED
@@ -1,9 +1,98 @@
1
- import { ESLintUtils } from '@typescript-eslint/utils';
1
+ import type { Definition, Scope, ScopeVariable } from '@typescript-eslint/scope-manager';
2
+ import type { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/types';
3
+ type Variable = ScopeVariable;
4
+ type Range = readonly [number, number];
5
+ type NodeOrToken = TSESTree.Node | TSESTree.Token;
6
+ /** The part of a linter's source code the rules read. */
7
+ interface SourceCode {
8
+ getAncestors(node: TSESTree.Node): TSESTree.Node[];
9
+ getCommentsInside(node: TSESTree.Node): TSESTree.Comment[];
10
+ getFirstToken(node: TSESTree.Node): TSESTree.Token | null;
11
+ getScope(node: TSESTree.Node): Scope;
12
+ getText(node?: NodeOrToken): string;
13
+ getTokenAfter(node: NodeOrToken, options: {
14
+ readonly includeComments: true;
15
+ }): TSESTree.Token | null;
16
+ getTokens(node: TSESTree.Node): TSESTree.Token[];
17
+ }
18
+ interface RuleFix {
19
+ readonly range: Range;
20
+ readonly text: string;
21
+ }
22
+ interface RuleFixer {
23
+ insertTextAfter(node: NodeOrToken, text: string): RuleFix;
24
+ insertTextBefore(node: NodeOrToken, text: string): RuleFix;
25
+ removeRange(range: Range): RuleFix;
26
+ replaceText(node: NodeOrToken, text: string): RuleFix;
27
+ replaceTextRange(range: Range, text: string): RuleFix;
28
+ }
29
+ type ReportFixFunction = (fixer: RuleFixer) => Iterable<RuleFix> | RuleFix | null;
30
+ type SuggestionDescriptor<MessageIds extends string> = {
31
+ readonly data?: Readonly<Record<string, unknown>>;
32
+ readonly fix: ReportFixFunction;
33
+ readonly messageId: MessageIds;
34
+ };
35
+ type ReportDescriptor<MessageIds extends string> = {
36
+ readonly data?: Readonly<Record<string, unknown>>;
37
+ readonly fix?: ReportFixFunction | null;
38
+ readonly messageId: MessageIds;
39
+ readonly node: NodeOrToken;
40
+ readonly suggest?: readonly SuggestionDescriptor<MessageIds>[] | null;
41
+ };
42
+ interface RuleContext<MessageIds extends string, Options extends readonly unknown[]> {
43
+ readonly options: Options;
44
+ readonly sourceCode: SourceCode;
45
+ report(descriptor: ReportDescriptor<MessageIds>): void;
46
+ }
47
+ type NodeOfType<Type extends AST_NODE_TYPES> = Extract<TSESTree.Node, {
48
+ type: Type;
49
+ }>;
50
+ type NodeListeners = {
51
+ readonly [Type in AST_NODE_TYPES]?: (node: NodeOfType<Type>) => void;
52
+ };
53
+ type ExitListeners = {
54
+ readonly [Type in AST_NODE_TYPES as `${Type}:exit`]?: (node: NodeOfType<Type>) => void;
55
+ };
56
+ /** A node type names the node its listener receives; any other selector annotates its parameter itself. */
57
+ interface RuleListener extends NodeListeners, ExitListeners {
58
+ readonly [selector: string]: ((node: never) => void) | undefined;
59
+ }
60
+ type RuleMeta<MessageIds extends string, Options extends readonly unknown[]> = {
61
+ readonly defaultOptions?: Options;
62
+ readonly docs: {
63
+ readonly description: string;
64
+ readonly url?: string;
65
+ };
66
+ readonly fixable?: 'code' | 'whitespace';
67
+ readonly hasSuggestions?: boolean;
68
+ readonly messages: Readonly<Record<MessageIds, string>>;
69
+ readonly schema: readonly unknown[];
70
+ readonly type: 'layout' | 'problem' | 'suggestion';
71
+ };
72
+ type RuleModule<MessageIds extends string, Options extends readonly unknown[]> = {
73
+ readonly create: (context: RuleContext<MessageIds, Options>) => RuleListener;
74
+ readonly defaultOptions: Options;
75
+ readonly meta: RuleMeta<MessageIds, Options> & {
76
+ readonly docs: {
77
+ readonly url: string;
78
+ };
79
+ };
80
+ readonly name: string;
81
+ };
82
+ type RuleDefinition<MessageIds extends string, Options extends readonly unknown[]> = {
83
+ readonly create: (context: RuleContext<MessageIds, Options>, options: Options) => RuleListener;
84
+ readonly meta: RuleMeta<MessageIds, Options>;
85
+ readonly name: string;
86
+ };
2
87
  /**
3
88
  * Every rule documents itself in the package README; the anchor is the rule name, so a report always names the
4
89
  * section that explains the law behind it.
5
90
  */
6
- declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
7
- name: string;
8
- };
9
- export { createRule };
91
+ declare function createRule<Options extends readonly unknown[], MessageIds extends string>({ create, meta, name, }: RuleDefinition<MessageIds, Options>): RuleModule<MessageIds, Options>;
92
+ /**
93
+ * The variable a name resolves to, from the scope it stands in outwards: the walk of the `findVariable` helper
94
+ * ESLint plugins share, over the scope objects both linters give a rule.
95
+ */
96
+ declare function findVariable(initial: Scope, node: TSESTree.Identifier | string): Variable | null;
97
+ export type { Definition, ReportFixFunction, RuleFix, RuleFixer, RuleModule, Scope, SourceCode, Variable };
98
+ export { createRule, findVariable };
package/dist/rule.js CHANGED
@@ -1,2 +1,2 @@
1
- import{ESLintUtils as e}from"@typescript-eslint/utils";const o=e.RuleCreator(t=>`@opetope/lint/README.md#${t}`);export{o as createRule};
1
+ function c(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function s(n,e){if(!c(n)||!c(e))return e;const r=structuredClone(n);for(const[t,o]of Object.entries(e)){const u=Object.hasOwn(n,t)?n[t]:void 0;Object.defineProperty(r,t,{configurable:!0,enumerable:!0,value:s(u,o),writable:!0})}return r}function f(n,e){return n.map((r,t)=>e[t]===void 0?structuredClone(r):s(r,e[t]))}function p({create:n,meta:e,name:r}){const t=e.defaultOptions??[];return{create:o=>n(o,f(t,o.options)),defaultOptions:t,meta:{...e,docs:{...e.docs,url:`@opetope/lint/README.md#${r}`}},name:r}}function a(n,e){const[r]=e.range;let t=n,o=!0;for(;o;){o=!1;for(const u of t.childScopes){const[i,l]=u.block.range;if(i<=r&&r<l){t=u,o=!0;break}}}return t}function d(n,e){const r=typeof e=="string"?e:e.name;let t=typeof e=="string"?n:a(n,e);for(;t!==null;){const o=t.set.get(r);if(o!==void 0)return o;t=t.upper}return null}export{p as createRule,d as findVariable};
2
2
  //# sourceMappingURL=rule.js.map
package/dist/rule.js.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"rule.js","sources":["../src/rule.ts"],"sourcesContent":["import type { Definition, Scope, ScopeVariable } from '@typescript-eslint/scope-manager';\nimport type { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/types';\n\n/*\n * The rule API these rules are written against, and nothing more: the members ESLint and Oxlint both hand a plugin\n * rule. It is declared here rather than taken from `@typescript-eslint/utils`, whose peers `eslint` and `typescript`\n * npm would install beside an Oxlint host that needs neither. The node types and the scope definitions come from\n * `@typescript-eslint/types` and `@typescript-eslint/scope-manager`, which have no peers.\n */\n\ntype Variable = ScopeVariable;\ntype Range = readonly [number, number];\ntype NodeOrToken = TSESTree.Node | TSESTree.Token;\n\n/** The part of a linter's source code the rules read. */\ninterface SourceCode {\n getAncestors(node: TSESTree.Node): TSESTree.Node[];\n getCommentsInside(node: TSESTree.Node): TSESTree.Comment[];\n getFirstToken(node: TSESTree.Node): TSESTree.Token | null;\n getScope(node: TSESTree.Node): Scope;\n getText(node?: NodeOrToken): string;\n getTokenAfter(node: NodeOrToken, options: { readonly includeComments: true }): TSESTree.Token | null;\n getTokens(node: TSESTree.Node): TSESTree.Token[];\n}\n\ninterface RuleFix {\n readonly range: Range;\n readonly text: string;\n}\n\ninterface RuleFixer {\n insertTextAfter(node: NodeOrToken, text: string): RuleFix;\n insertTextBefore(node: NodeOrToken, text: string): RuleFix;\n removeRange(range: Range): RuleFix;\n replaceText(node: NodeOrToken, text: string): RuleFix;\n replaceTextRange(range: Range, text: string): RuleFix;\n}\n\ntype ReportFixFunction = (fixer: RuleFixer) => Iterable<RuleFix> | RuleFix | null;\n\ntype SuggestionDescriptor<MessageIds extends string> = {\n readonly data?: Readonly<Record<string, unknown>>;\n readonly fix: ReportFixFunction;\n readonly messageId: MessageIds;\n};\n\ntype ReportDescriptor<MessageIds extends string> = {\n readonly data?: Readonly<Record<string, unknown>>;\n readonly fix?: ReportFixFunction | null;\n readonly messageId: MessageIds;\n readonly node: NodeOrToken;\n readonly suggest?: readonly SuggestionDescriptor<MessageIds>[] | null;\n};\n\ninterface RuleContext<MessageIds extends string, Options extends readonly unknown[]> {\n readonly options: Options;\n readonly sourceCode: SourceCode;\n report(descriptor: ReportDescriptor<MessageIds>): void;\n}\n\ntype NodeOfType<Type extends AST_NODE_TYPES> = Extract<TSESTree.Node, { type: Type }>;\n\ntype NodeListeners = {\n readonly [Type in AST_NODE_TYPES]?: (node: NodeOfType<Type>) => void;\n};\n\ntype ExitListeners = {\n readonly [Type in AST_NODE_TYPES as `${Type}:exit`]?: (node: NodeOfType<Type>) => void;\n};\n\n/** A node type names the node its listener receives; any other selector annotates its parameter itself. */\ninterface RuleListener extends NodeListeners, ExitListeners {\n // oxlint-disable-next-line typescript/consistent-indexed-object-style -- the named listeners it extends take precedence over this signature only inside one interface.\n readonly [selector: string]: ((node: never) => void) | undefined;\n}\n\ntype RuleMeta<MessageIds extends string, Options extends readonly unknown[]> = {\n readonly defaultOptions?: Options;\n readonly docs: { readonly description: string; readonly url?: string };\n readonly fixable?: 'code' | 'whitespace';\n readonly hasSuggestions?: boolean;\n readonly messages: Readonly<Record<MessageIds, string>>;\n readonly schema: readonly unknown[];\n readonly type: 'layout' | 'problem' | 'suggestion';\n};\n\ntype RuleModule<MessageIds extends string, Options extends readonly unknown[]> = {\n readonly create: (context: RuleContext<MessageIds, Options>) => RuleListener;\n readonly defaultOptions: Options;\n readonly meta: RuleMeta<MessageIds, Options> & { readonly docs: { readonly url: string } };\n readonly name: string;\n};\n\ntype RuleDefinition<MessageIds extends string, Options extends readonly unknown[]> = {\n readonly create: (context: RuleContext<MessageIds, Options>, options: Options) => RuleListener;\n readonly meta: RuleMeta<MessageIds, Options>;\n readonly name: string;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * A record written over its defaults, key by key and record by record; any other value replaces the default. The\n * defaults are cloned first, as `applyDefault` of `@typescript-eslint/utils` clones them, so no answer shares a nested\n * record with `meta.defaultOptions`; a written key is defined as an own property, so a `__proto__` a configuration\n * wrote stays a key and never becomes the prototype.\n */\nfunction merged(defaults: unknown, written: unknown): unknown {\n if (!isRecord(defaults) || !isRecord(written)) return written;\n\n const result = structuredClone(defaults);\n\n for (const [key, value] of Object.entries(written)) {\n const fallback = Object.hasOwn(defaults, key) ? defaults[key] : undefined;\n\n Object.defineProperty(result, key, {\n configurable: true,\n enumerable: true,\n value: merged(fallback, value),\n writable: true,\n });\n }\n\n return result;\n}\n\n/** The options a rule runs with: each one the project wrote, over the default in its place. */\nfunction optionsOf<Options extends readonly unknown[]>(defaults: Options, written: readonly unknown[]): Options {\n return defaults.map((fallback, index) =>\n written[index] === undefined ? structuredClone(fallback) : merged(fallback, written[index]),\n ) as unknown as Options;\n}\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 */\nfunction createRule<Options extends readonly unknown[], MessageIds extends string>({\n create,\n meta,\n name,\n}: RuleDefinition<MessageIds, Options>): RuleModule<MessageIds, Options> {\n const defaultOptions = meta.defaultOptions ?? ([] as unknown as Options);\n\n return {\n create: context => create(context, optionsOf(defaultOptions, context.options)),\n defaultOptions,\n meta: { ...meta, docs: { ...meta.docs, url: `@opetope/lint/README.md#${name}` } },\n name,\n };\n}\n\n/** The scope a node stands in: the innermost scope under `initial` whose block holds the node's first character. */\nfunction innermostScope(initial: Scope, node: TSESTree.Node): Scope {\n const [location] = node.range;\n let scope = initial;\n let found = true;\n\n while (found) {\n found = false;\n\n for (const child of scope.childScopes) {\n const [start, end] = child.block.range;\n\n if (start <= location && location < end) {\n scope = child;\n found = true;\n break;\n }\n }\n }\n\n return scope;\n}\n\n/**\n * The variable a name resolves to, from the scope it stands in outwards: the walk of the `findVariable` helper\n * ESLint plugins share, over the scope objects both linters give a rule.\n */\nfunction findVariable(initial: Scope, node: TSESTree.Identifier | string): Variable | null {\n const name = typeof node === 'string' ? node : node.name;\n let scope: Scope | null = typeof node === 'string' ? initial : innermostScope(initial, node);\n\n while (scope !== null) {\n const variable = scope.set.get(name);\n\n if (variable !== undefined) return variable;\n\n scope = scope.upper;\n }\n\n return null;\n}\n\nexport type { Definition, ReportFixFunction, RuleFix, RuleFixer, RuleModule, Scope, SourceCode, Variable };\nexport { createRule, findVariable };\n"],"names":["isRecord","value","merged","defaults","written","result","key","fallback","optionsOf","index","createRule","create","meta","name","defaultOptions","context","innermostScope","initial","node","location","scope","found","child","start","end","findVariable","variable"],"mappings":"AAmGA,SAASA,EAASC,EAAc,CAC9B,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAQA,SAASC,EAAOC,EAAmBC,EAAgB,CACjD,GAAI,CAACJ,EAASG,CAAQ,GAAK,CAACH,EAASI,CAAO,EAAG,OAAOA,EAEtD,MAAMC,EAAS,gBAAgBF,CAAQ,EAEvC,SAAW,CAACG,EAAKL,CAAK,IAAK,OAAO,QAAQG,CAAO,EAAG,CAClD,MAAMG,EAAW,OAAO,OAAOJ,EAAUG,CAAG,EAAIH,EAASG,CAAG,EAAI,OAEhE,OAAO,eAAeD,EAAQC,EAAK,CACjC,aAAc,GACd,WAAY,GACZ,MAAOJ,EAAOK,EAAUN,CAAK,EAC7B,SAAU,EACX,CAAA,CACH,CAEA,OAAOI,CACT,CAGA,SAASG,EAA8CL,EAAmBC,EAA2B,CACnG,OAAOD,EAAS,IAAI,CAACI,EAAUE,IAC7BL,EAAQK,CAAK,IAAM,OAAY,gBAAgBF,CAAQ,EAAIL,EAAOK,EAAUH,EAAQK,CAAK,CAAC,CAAC,CAE/F,CAMA,SAASC,EAA0E,CACjF,OAAAC,EACA,KAAAC,EACA,KAAAC,CAAI,EACgC,CACpC,MAAMC,EAAiBF,EAAK,gBAAmB,CAAA,EAE/C,MAAO,CACL,OAAQG,GAAWJ,EAAOI,EAASP,EAAUM,EAAgBC,EAAQ,OAAO,CAAC,EAC7E,eAAAD,EACA,KAAM,CAAE,GAAGF,EAAM,KAAM,CAAE,GAAGA,EAAK,KAAM,IAAK,2BAA2BC,CAAI,EAAE,CAAE,EAC/E,KAAAA,EAEJ,CAGA,SAASG,EAAeC,EAAgBC,EAAmB,CACzD,KAAM,CAACC,CAAQ,EAAID,EAAK,MACxB,IAAIE,EAAQH,EACRI,EAAQ,GAEZ,KAAOA,GAAO,CACZA,EAAQ,GAER,UAAWC,KAASF,EAAM,YAAa,CACrC,KAAM,CAACG,EAAOC,CAAG,EAAIF,EAAM,MAAM,MAEjC,GAAIC,GAASJ,GAAYA,EAAWK,EAAK,CACvCJ,EAAQE,EACRD,EAAQ,GACR,KACF,CACF,CACF,CAEA,OAAOD,CACT,CAMA,SAASK,EAAaR,EAAgBC,EAAkC,CACtE,MAAML,EAAO,OAAOK,GAAS,SAAWA,EAAOA,EAAK,KACpD,IAAIE,EAAsB,OAAOF,GAAS,SAAWD,EAAUD,EAAeC,EAASC,CAAI,EAE3F,KAAOE,IAAU,MAAM,CACrB,MAAMM,EAAWN,EAAM,IAAI,IAAIP,CAAI,EAEnC,GAAIa,IAAa,OAAW,OAAOA,EAEnCN,EAAQA,EAAM,KAChB,CAEA,OAAO,IACT"}
@@ -1,6 +1,3 @@
1
- import type { TSESLint } from '@typescript-eslint/utils';
2
1
  type MessageIds = 'captureCommandCleanup' | 'parallelCommandCleanup';
3
- declare const captureCommandCleanup: TSESLint.RuleModule<MessageIds, [], unknown, TSESLint.RuleListener> & {
4
- name: string;
5
- };
2
+ declare const captureCommandCleanup: import("../rule.js").RuleModule<MessageIds, []>;
6
3
  export { captureCommandCleanup };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as l}from"@typescript-eslint/utils";import{within as u,findProperty as w}from"../ast.js";import{contextMembers as g,fromContext as f}from"../context-members.js";import{methodPath as C,isModelContextFunction as b,commandBody as x}from"../declaration-ingress.js";import{unwrap as d,enclosingFunction as m}from"../model-bindings.js";import{createRule as S}from"../rule.js";const E=["update","signal","rethrowIfCancelled"],T=0;function A(a){const e=a.arguments[1],n=e===void 0?void 0:d(e),r=n?.type===l.ObjectExpression?w(n,"concurrency"):void 0,t=r===void 0?void 0:d(r.value);return t?.type===l.Literal&&t.value==="parallel"}function h(a,e,n,r){return e.type!==l.MemberExpression||e.computed||e.property.type!==l.Identifier||e.property.name!==r?!1:f(a,e.object,n,"signal")}function y(a){if(a.type===l.ThrowStatement||a.type===l.ReturnStatement)return!0;if(a.type!==l.BlockStatement)return!1;const e=a.body.at(-1);return e!==void 0&&y(e)}function I(a,e,n){if(e.type===l.IfStatement)return h(a,d(e.test),n,"aborted")&&y(e.consequent);if(e.type!==l.ExpressionStatement)return!1;const r=d(e.expression);return r.type!==l.CallExpression?!1:f(a,r.callee,n,"rethrowIfCancelled")||h(a,r.callee,n,"throwIfAborted")}function v(a,e,n,r){for(const t of e.body){if(u(n,t))return!1;if(I(a,t,r))return!0}return!1}function O(a,e,n,r){return a.getAncestors(n).some(t=>t.type===l.BlockStatement&&u(t,e.body)&&v(a,t,n,r))}function k(a,e,n){const r=m(a,e);return n.barriers.some(t=>u(t,e.block)&&m(a,t)===r)}function D(a,e,n,r,t,c){if(n===e.block||!k(a,e,c))return;if(n===e.finalizer)return"finally";const{handler:o}=e;if(!(o===null||n!==o))return O(a,o,r,t)?void 0:"catch"}function P(a,e,n,r,t){let c=e;for(const o of a.getAncestors(e).reverse()){if(o===n)return;const i=o.type===l.TryStatement?D(a,o,c,e,r,t):void 0;if(i!==void 0)return i;c=o}}const B=S({create(a){const e=a.sourceCode,n={barriers:[],calls:[],declarators:[]},r=(t,c)=>{const o=g(e,t,T,E,n.declarators),i=c?"parallelCommandCleanup":"captureCommandCleanup";for(const s of n.calls){if(!u(s,t)||!f(e,s.callee,o,"update"))continue;const p=P(e,s,t,o,n);p!==void 0&&a.report({data:{clause:p},messageId:i,node:s})}};return{AwaitExpression(t){n.barriers.push(t)},CallExpression(t){n.calls.push(t)},ForOfStatement(t){t.await&&n.barriers.push(t)},"Program:exit"(){for(const t of n.calls){const c=C(e,t.callee);if(c?.method!=="command"||!b(e,c.fn))continue;const o=x(e,t);o!==void 0&&r(o,A(t))}},VariableDeclarator(t){n.declarators.push(t)}}},meta:{docs:{description:"A command writes the cleanup it owes after an `await` through a commit, not through its writer."},messages:{captureCommandCleanup:"This write is in the `{{clause}}` of a `try` that awaits, and the writer of a command lives only as long as its caller: once the call is cancelled the write is dropped without a record. A cleanup that must run for a cancelled call \u2014 a busy flag, a pending mark \u2014 goes through `const commit = execution.capture(state)` taken before the `await` and `commit.update(\u2026)` here. An answer that must not land after cancellation \u2014 a failure, a result \u2014 belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D319, D330).",parallelCommandCleanup:"This write is in the `{{clause}}` of a `try` that awaits in a `concurrency: 'parallel'` command, and once the call is cancelled it is dropped without a record. A commit does not fix it: calls of this command overlap, so the cleanup of a cancelled call would clear state another call still holds, and a flag shared by parallel calls is racy either way. Keep such state per call \u2014 keyed by its input \u2014 or leave progress to the consumer's `inFlight`. An answer that must not land after cancellation belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D330)."},schema:[],type:"problem"},name:"capture-command-cleanup"});export{B as captureCommandCleanup};
1
+ import{AST_NODE_TYPES as l}from"@typescript-eslint/types";import{within as u,findProperty as w}from"../ast.js";import{contextMembers as g,fromContext as f}from"../context-members.js";import{methodPath as C,isModelContextFunction as b,commandBody as x}from"../declaration-ingress.js";import{unwrap as d,enclosingFunction as m}from"../model-bindings.js";import{createRule as S}from"../rule.js";const E=["update","signal","rethrowIfCancelled"],T=0;function A(a){const e=a.arguments[1],n=e===void 0?void 0:d(e),r=n?.type===l.ObjectExpression?w(n,"concurrency"):void 0,t=r===void 0?void 0:d(r.value);return t?.type===l.Literal&&t.value==="parallel"}function h(a,e,n,r){return e.type!==l.MemberExpression||e.computed||e.property.type!==l.Identifier||e.property.name!==r?!1:f(a,e.object,n,"signal")}function y(a){if(a.type===l.ThrowStatement||a.type===l.ReturnStatement)return!0;if(a.type!==l.BlockStatement)return!1;const e=a.body.at(-1);return e!==void 0&&y(e)}function I(a,e,n){if(e.type===l.IfStatement)return h(a,d(e.test),n,"aborted")&&y(e.consequent);if(e.type!==l.ExpressionStatement)return!1;const r=d(e.expression);return r.type!==l.CallExpression?!1:f(a,r.callee,n,"rethrowIfCancelled")||h(a,r.callee,n,"throwIfAborted")}function v(a,e,n,r){for(const t of e.body){if(u(n,t))return!1;if(I(a,t,r))return!0}return!1}function O(a,e,n,r){return a.getAncestors(n).some(t=>t.type===l.BlockStatement&&u(t,e.body)&&v(a,t,n,r))}function k(a,e,n){const r=m(a,e);return n.barriers.some(t=>u(t,e.block)&&m(a,t)===r)}function D(a,e,n,r,t,c){if(n===e.block||!k(a,e,c))return;if(n===e.finalizer)return"finally";const{handler:o}=e;if(!(o===null||n!==o))return O(a,o,r,t)?void 0:"catch"}function P(a,e,n,r,t){let c=e;for(const o of a.getAncestors(e).reverse()){if(o===n)return;const i=o.type===l.TryStatement?D(a,o,c,e,r,t):void 0;if(i!==void 0)return i;c=o}}const B=S({create(a){const e=a.sourceCode,n={barriers:[],calls:[],declarators:[]},r=(t,c)=>{const o=g(e,t,T,E,n.declarators),i=c?"parallelCommandCleanup":"captureCommandCleanup";for(const s of n.calls){if(!u(s,t)||!f(e,s.callee,o,"update"))continue;const p=P(e,s,t,o,n);p!==void 0&&a.report({data:{clause:p},messageId:i,node:s})}};return{AwaitExpression(t){n.barriers.push(t)},CallExpression(t){n.calls.push(t)},ForOfStatement(t){t.await&&n.barriers.push(t)},"Program:exit"(){for(const t of n.calls){const c=C(e,t.callee);if(c?.method!=="command"||!b(e,c.fn))continue;const o=x(e,t);o!==void 0&&r(o,A(t))}},VariableDeclarator(t){n.declarators.push(t)}}},meta:{docs:{description:"A command writes the cleanup it owes after an `await` through a commit, not through its writer."},messages:{captureCommandCleanup:"This write is in the `{{clause}}` of a `try` that awaits, and the writer of a command lives only as long as its caller: once the call is cancelled the write is dropped without a record. A cleanup that must run for a cancelled call \u2014 a busy flag, a pending mark \u2014 goes through `const commit = execution.capture(state)` taken before the `await` and `commit.update(\u2026)` here. An answer that must not land after cancellation \u2014 a failure, a result \u2014 belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D319, D330).",parallelCommandCleanup:"This write is in the `{{clause}}` of a `try` that awaits in a `concurrency: 'parallel'` command, and once the call is cancelled it is dropped without a record. A commit does not fix it: calls of this command overlap, so the cleanup of a cancelled call would clear state another call still holds, and a flag shared by parallel calls is racy either way. Keep such state per call \u2014 keyed by its input \u2014 or leave progress to the consumer's `inFlight`. An answer that must not land after cancellation belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D330)."},schema:[],type:"problem"},name:"capture-command-cleanup"});export{B as captureCommandCleanup};
2
2
  //# sourceMappingURL=capture-command-cleanup.js.map