@opetope/lint 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +75 -87
  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 +8 -7
  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":"require-declared-models.js","sources":["../../src/rules/require-declared-models.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty } from '../ast';\nimport { bodyRecordEntries } from '../feature-body-records';\nimport type { FunctionNode, ModelBinding } from '../model-bindings';\nimport { enclosingFunction, isApi, isFunction, isSlot, localValue, modelBinding, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'missingWrapper' | 'undeclaredUiModel';\ntype Requirements = ReadonlySet<ModelBinding> | undefined;\n\nfunction plannedModel(value: TSESTree.Node): TSESTree.Node | undefined {\n if (value.type === AST_NODE_TYPES.CallExpression) return value.arguments[0];\n if (value.type === AST_NODE_TYPES.ArrayExpression) return value.elements[0] ?? undefined;\n return undefined;\n}\n\nconst requireDeclaredModels = createRule<readonly [], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const calls: TSESTree.CallExpression[] = [];\n const returns = new Map<FunctionNode, TSESTree.Node[]>();\n const wrappers = new Map<TSESTree.Node, Requirements>();\n const readers = new Map<FunctionNode, Requirements>();\n const granted = new Set<ModelBinding>();\n\n const api = (node: TSESTree.Node, name: string): boolean => isApi(source, node, '@opetope/react', name);\n\n const requirements = (node: TSESTree.Node | undefined): Requirements => {\n if (node === undefined) return undefined;\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n const bindings = new Set<ModelBinding>();\n\n for (const element of value.elements) {\n if (element === null) continue;\n\n const binding = modelBinding(source, element);\n\n if (binding === undefined) return undefined;\n\n bindings.add(binding);\n }\n\n return bindings;\n };\n\n const collectWrapper = (node: TSESTree.CallExpression): void => {\n const inner = unwrap(node.callee);\n\n if (inner.type !== AST_NODE_TYPES.CallExpression || !api(inner.callee, 'requiresModels')) return;\n\n const models = requirements(inner.arguments[0]);\n\n wrappers.set(node, models);\n const [argument] = node.arguments;\n\n if (argument === undefined) return;\n\n const component = localValue(source, argument);\n\n if (isFunction(component)) readers.set(component, models);\n };\n\n const grantModels = (node: TSESTree.ArrayExpression): void => {\n for (const element of node.elements) {\n if (element === null) continue;\n\n const value = localValue(source, element);\n const model = plannedModel(value);\n const binding = model === undefined ? undefined : modelBinding(source, model);\n\n if (binding !== undefined) granted.add(binding);\n }\n };\n\n const declaredComponent = (value: TSESTree.Node): boolean =>\n wrappers.has(value) || (isFunction(value) && readers.has(value));\n\n const checkComponent = (component: TSESTree.Property): void => {\n const value = localValue(source, component.value);\n if (declaredComponent(value)) return;\n // Imported components and parameters are unknown, not verified or rejected by this single-module rule.\n if (value.type === AST_NODE_TYPES.Identifier || value.type === AST_NODE_TYPES.MemberExpression) return;\n context.report({ messageId: 'missingWrapper', node: component });\n };\n\n const checkContribution = (object: TSESTree.ObjectExpression): void => {\n const component = findProperty(object, 'Component');\n const models = findProperty(object, 'models');\n\n if (component === undefined || models === undefined) return;\n\n const plans = localValue(source, models.value);\n\n if (plans.type !== AST_NODE_TYPES.ArrayExpression || plans.elements.length === 0) return;\n\n grantModels(plans);\n checkComponent(component);\n };\n\n const returnedValues = (value: FunctionNode): readonly TSESTree.Node[] => {\n if (value.body === undefined) return [];\n return value.body.type === AST_NODE_TYPES.BlockStatement ? (returns.get(value) ?? []) : [value.body];\n };\n\n const contributionValues = (node: TSESTree.Node, seen = new Set<TSESTree.Node>()): void => {\n const value = localValue(source, node);\n\n if (seen.has(value)) return;\n\n seen.add(value);\n if (value.type === AST_NODE_TYPES.ObjectExpression) checkContribution(value);\n else if (isFunction(value)) {\n for (const returned of returnedValues(value)) contributionValues(returned, seen);\n }\n };\n\n const checkBodyRecord = (call: TSESTree.CallExpression): void => {\n for (const entry of bodyRecordEntries(source, call)) {\n if (entry.implementation !== undefined) contributionValues(entry.implementation);\n }\n };\n\n const declaresRead = (node: TSESTree.Node, binding: ModelBinding): boolean => {\n const fn = enclosingFunction(source, node);\n if (fn === undefined || !readers.has(fn)) return false;\n const declared = readers.get(fn);\n return declared === undefined || declared.has(binding);\n };\n\n const checkRead = (node: TSESTree.CallExpression): void => {\n if (!api(node.callee, 'useModel')) return;\n\n const [argument] = node.arguments;\n const binding = argument === undefined ? undefined : modelBinding(source, argument);\n\n if (binding === undefined || !granted.has(binding)) return;\n\n if (declaresRead(node, binding)) return;\n\n context.report({ data: { name: source.getText(argument) }, messageId: 'undeclaredUiModel', node });\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n ReturnStatement(node): void {\n const fn = enclosingFunction(source, node);\n\n if (fn === undefined || node.argument === null) return;\n\n const values = returns.get(fn) ?? [];\n\n values.push(node.argument);\n returns.set(fn, values);\n },\n 'Program:exit'(): void {\n for (const call of calls) collectWrapper(call);\n for (const call of calls) {\n const contribution = call.arguments[1];\n\n if (contribution !== undefined && isSlot(source, call.callee)) contributionValues(contribution);\n checkBodyRecord(call);\n }\n for (const call of calls) checkRead(call);\n },\n };\n },\n meta: {\n docs: { description: 'Each visible reader of a per-mount UI model declares it with requiresModels.' },\n messages: {\n missingWrapper:\n 'This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.',\n undeclaredUiModel:\n 'A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'require-declared-models',\n});\n\nexport { requireDeclaredModels };\n"],"names":["plannedModel","value","AST_NODE_TYPES","requireDeclaredModels","createRule","context","source","calls","returns","wrappers","readers","granted","api","node","name","isApi","requirements","localValue","bindings","element","binding","modelBinding","collectWrapper","inner","unwrap","models","argument","component","isFunction","grantModels","model","declaredComponent","checkComponent","checkContribution","object","findProperty","plans","returnedValues","contributionValues","seen","returned","checkBodyRecord","call","entry","bodyRecordEntries","declaresRead","fn","enclosingFunction","declared","checkRead","values","contribution","isSlot"],"mappings":"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"}
1
+ {"version":3,"file":"require-declared-models.js","sources":["../../src/rules/require-declared-models.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { findProperty } from '../ast';\nimport { bodyRecordEntries } from '../feature-body-records';\nimport type { FunctionNode, ModelBinding } from '../model-bindings';\nimport { enclosingFunction, isApi, isFunction, isSlot, localValue, modelBinding, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'missingWrapper' | 'undeclaredUiModel';\ntype Requirements = ReadonlySet<ModelBinding> | undefined;\n\nfunction plannedModel(value: TSESTree.Node): TSESTree.Node | undefined {\n if (value.type === AST_NODE_TYPES.CallExpression) return value.arguments[0];\n if (value.type === AST_NODE_TYPES.ArrayExpression) return value.elements[0] ?? undefined;\n return undefined;\n}\n\nconst requireDeclaredModels = createRule<readonly [], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const calls: TSESTree.CallExpression[] = [];\n const returns = new Map<FunctionNode, TSESTree.Node[]>();\n const wrappers = new Map<TSESTree.Node, Requirements>();\n const readers = new Map<FunctionNode, Requirements>();\n const granted = new Set<ModelBinding>();\n\n const api = (node: TSESTree.Node, name: string): boolean => isApi(source, node, '@opetope/react', name);\n\n const requirements = (node: TSESTree.Node | undefined): Requirements => {\n if (node === undefined) return undefined;\n\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n const bindings = new Set<ModelBinding>();\n\n for (const element of value.elements) {\n if (element === null) continue;\n\n const binding = modelBinding(source, element);\n\n if (binding === undefined) return undefined;\n\n bindings.add(binding);\n }\n\n return bindings;\n };\n\n const collectWrapper = (node: TSESTree.CallExpression): void => {\n const inner = unwrap(node.callee);\n\n if (inner.type !== AST_NODE_TYPES.CallExpression || !api(inner.callee, 'requiresModels')) return;\n\n const models = requirements(inner.arguments[0]);\n\n wrappers.set(node, models);\n const [argument] = node.arguments;\n\n if (argument === undefined) return;\n\n const component = localValue(source, argument);\n\n if (isFunction(component)) readers.set(component, models);\n };\n\n const grantModels = (node: TSESTree.ArrayExpression): void => {\n for (const element of node.elements) {\n if (element === null) continue;\n\n const value = localValue(source, element);\n const model = plannedModel(value);\n const binding = model === undefined ? undefined : modelBinding(source, model);\n\n if (binding !== undefined) granted.add(binding);\n }\n };\n\n const declaredComponent = (value: TSESTree.Node): boolean =>\n wrappers.has(value) || (isFunction(value) && readers.has(value));\n\n const checkComponent = (component: TSESTree.Property): void => {\n const value = localValue(source, component.value);\n if (declaredComponent(value)) return;\n // Imported components and parameters are unknown, not verified or rejected by this single-module rule.\n if (value.type === AST_NODE_TYPES.Identifier || value.type === AST_NODE_TYPES.MemberExpression) return;\n context.report({ messageId: 'missingWrapper', node: component });\n };\n\n const checkContribution = (object: TSESTree.ObjectExpression): void => {\n const component = findProperty(object, 'Component');\n const models = findProperty(object, 'models');\n\n if (component === undefined || models === undefined) return;\n\n const plans = localValue(source, models.value);\n\n if (plans.type !== AST_NODE_TYPES.ArrayExpression || plans.elements.length === 0) return;\n\n grantModels(plans);\n checkComponent(component);\n };\n\n const returnedValues = (value: FunctionNode): readonly TSESTree.Node[] => {\n if (value.body === undefined) return [];\n return value.body.type === AST_NODE_TYPES.BlockStatement ? (returns.get(value) ?? []) : [value.body];\n };\n\n const contributionValues = (node: TSESTree.Node, seen = new Set<TSESTree.Node>()): void => {\n const value = localValue(source, node);\n\n if (seen.has(value)) return;\n\n seen.add(value);\n if (value.type === AST_NODE_TYPES.ObjectExpression) checkContribution(value);\n else if (isFunction(value)) {\n for (const returned of returnedValues(value)) contributionValues(returned, seen);\n }\n };\n\n const checkBodyRecord = (call: TSESTree.CallExpression): void => {\n for (const entry of bodyRecordEntries(source, call)) {\n if (entry.implementation !== undefined) contributionValues(entry.implementation);\n }\n };\n\n const declaresRead = (node: TSESTree.Node, binding: ModelBinding): boolean => {\n const fn = enclosingFunction(source, node);\n if (fn === undefined || !readers.has(fn)) return false;\n const declared = readers.get(fn);\n return declared === undefined || declared.has(binding);\n };\n\n const checkRead = (node: TSESTree.CallExpression): void => {\n if (!api(node.callee, 'useModel')) return;\n\n const [argument] = node.arguments;\n const binding = argument === undefined ? undefined : modelBinding(source, argument);\n\n if (binding === undefined || !granted.has(binding)) return;\n\n if (declaresRead(node, binding)) return;\n\n context.report({ data: { name: source.getText(argument) }, messageId: 'undeclaredUiModel', node });\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n ReturnStatement(node): void {\n const fn = enclosingFunction(source, node);\n\n if (fn === undefined || node.argument === null) return;\n\n const values = returns.get(fn) ?? [];\n\n values.push(node.argument);\n returns.set(fn, values);\n },\n 'Program:exit'(): void {\n for (const call of calls) collectWrapper(call);\n for (const call of calls) {\n const contribution = call.arguments[1];\n\n if (contribution !== undefined && isSlot(source, call.callee)) contributionValues(contribution);\n checkBodyRecord(call);\n }\n for (const call of calls) checkRead(call);\n },\n };\n },\n meta: {\n docs: { description: 'Each visible reader of a per-mount UI model declares it with requiresModels.' },\n messages: {\n missingWrapper:\n 'This contribution grants models, so its Component must come from requiresModels([...]) of @opetope/react.',\n undeclaredUiModel:\n 'A per-mount model is read by a component or hook that declares it: wrap this reader in requiresModels([{{ name }}]), or read a model the feature owns.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'require-declared-models',\n});\n\nexport { requireDeclaredModels };\n"],"names":["plannedModel","value","AST_NODE_TYPES","requireDeclaredModels","createRule","context","source","calls","returns","wrappers","readers","granted","api","node","name","isApi","requirements","localValue","bindings","element","binding","modelBinding","collectWrapper","inner","unwrap","models","argument","component","isFunction","grantModels","model","declaredComponent","checkComponent","checkContribution","object","findProperty","plans","returnedValues","contributionValues","seen","returned","checkBodyRecord","call","entry","bodyRecordEntries","declaresRead","fn","enclosingFunction","declared","checkRead","values","contribution","isSlot"],"mappings":"wVAYA,SAASA,EAAaC,EAAoB,CACxC,GAAIA,EAAM,OAASC,EAAe,eAAgB,OAAOD,EAAM,UAAU,CAAC,EAC1E,GAAIA,EAAM,OAASC,EAAe,gBAAiB,OAAOD,EAAM,SAAS,CAAC,GAAK,MAEjF,CAEA,MAAME,EAAwBC,EAAoC,CAChE,OAAOC,EAAO,CACZ,MAAMC,EAASD,EAAQ,WACjBE,EAAmC,CAAA,EACnCC,EAAU,IAAI,IACdC,EAAW,IAAI,IACfC,EAAU,IAAI,IACdC,EAAU,IAAI,IAEdC,EAAM,CAACC,EAAqBC,IAA0BC,EAAMT,EAAQO,EAAM,iBAAkBC,CAAI,EAEhGE,EAAgBH,GAAiD,CACrE,GAAIA,IAAS,OAAW,OAExB,MAAMZ,EAAQgB,EAAWX,EAAQO,CAAI,EAErC,GAAIZ,EAAM,OAASC,EAAe,gBAAiB,OAEnD,MAAMgB,EAAW,IAAI,IAErB,UAAWC,KAAWlB,EAAM,SAAU,CACpC,GAAIkB,IAAY,KAAM,SAEtB,MAAMC,EAAUC,EAAaf,EAAQa,CAAO,EAE5C,GAAIC,IAAY,OAAW,OAE3BF,EAAS,IAAIE,CAAO,CACtB,CAEA,OAAOF,CACT,EAEMI,EAAkBT,GAAuC,CAC7D,MAAMU,EAAQC,EAAOX,EAAK,MAAM,EAEhC,GAAIU,EAAM,OAASrB,EAAe,gBAAkB,CAACU,EAAIW,EAAM,OAAQ,gBAAgB,EAAG,OAE1F,MAAME,EAAST,EAAaO,EAAM,UAAU,CAAC,CAAC,EAE9Cd,EAAS,IAAII,EAAMY,CAAM,EACzB,KAAM,CAACC,CAAQ,EAAIb,EAAK,UAExB,GAAIa,IAAa,OAAW,OAE5B,MAAMC,EAAYV,EAAWX,EAAQoB,CAAQ,EAEzCE,EAAWD,CAAS,GAAGjB,EAAQ,IAAIiB,EAAWF,CAAM,CAC1D,EAEMI,EAAehB,GAAwC,CAC3D,UAAWM,KAAWN,EAAK,SAAU,CACnC,GAAIM,IAAY,KAAM,SAEtB,MAAMlB,EAAQgB,EAAWX,EAAQa,CAAO,EAClCW,EAAQ9B,EAAaC,CAAK,EAC1BmB,EAAUU,IAAU,OAAY,OAAYT,EAAaf,EAAQwB,CAAK,EAExEV,IAAY,QAAWT,EAAQ,IAAIS,CAAO,CAChD,CACF,EAEMW,EAAqB9B,GACzBQ,EAAS,IAAIR,CAAK,GAAM2B,EAAW3B,CAAK,GAAKS,EAAQ,IAAIT,CAAK,EAE1D+B,EAAkBL,GAAsC,CAC5D,MAAM1B,EAAQgB,EAAWX,EAAQqB,EAAU,KAAK,EAC5CI,EAAkB9B,CAAK,GAEvBA,EAAM,OAASC,EAAe,YAAcD,EAAM,OAASC,EAAe,kBAC9EG,EAAQ,OAAO,CAAE,UAAW,iBAAkB,KAAMsB,EAAW,CACjE,EAEMM,EAAqBC,GAA2C,CACpE,MAAMP,EAAYQ,EAAaD,EAAQ,WAAW,EAC5CT,EAASU,EAAaD,EAAQ,QAAQ,EAE5C,GAAIP,IAAc,QAAaF,IAAW,OAAW,OAErD,MAAMW,EAAQnB,EAAWX,EAAQmB,EAAO,KAAK,EAEzCW,EAAM,OAASlC,EAAe,iBAAmBkC,EAAM,SAAS,SAAW,IAE/EP,EAAYO,CAAK,EACjBJ,EAAeL,CAAS,EAC1B,EAEMU,EAAkBpC,GAClBA,EAAM,OAAS,OAAkB,CAAA,EAC9BA,EAAM,KAAK,OAASC,EAAe,eAAkBM,EAAQ,IAAIP,CAAK,GAAK,CAAA,EAAM,CAACA,EAAM,IAAI,EAG/FqC,EAAqB,CAACzB,EAAqB0B,EAAO,IAAI,MAA8B,CACxF,MAAMtC,EAAQgB,EAAWX,EAAQO,CAAI,EAErC,GAAI,CAAA0B,EAAK,IAAItC,CAAK,GAGlB,GADAsC,EAAK,IAAItC,CAAK,EACVA,EAAM,OAASC,EAAe,iBAAkB+B,EAAkBhC,CAAK,UAClE2B,EAAW3B,CAAK,EACvB,UAAWuC,KAAYH,EAAepC,CAAK,EAAGqC,EAAmBE,EAAUD,CAAI,EAEnF,EAEME,EAAmBC,GAAuC,CAC9D,UAAWC,KAASC,EAAkBtC,EAAQoC,CAAI,EAC5CC,EAAM,iBAAmB,QAAWL,EAAmBK,EAAM,cAAc,CAEnF,EAEME,EAAe,CAAChC,EAAqBO,IAAkC,CAC3E,MAAM0B,EAAKC,EAAkBzC,EAAQO,CAAI,EACzC,GAAIiC,IAAO,QAAa,CAACpC,EAAQ,IAAIoC,CAAE,EAAG,MAAO,GACjD,MAAME,EAAWtC,EAAQ,IAAIoC,CAAE,EAC/B,OAAOE,IAAa,QAAaA,EAAS,IAAI5B,CAAO,CACvD,EAEM6B,EAAapC,GAAuC,CACxD,GAAI,CAACD,EAAIC,EAAK,OAAQ,UAAU,EAAG,OAEnC,KAAM,CAACa,CAAQ,EAAIb,EAAK,UAClBO,EAAUM,IAAa,OAAY,OAAYL,EAAaf,EAAQoB,CAAQ,EAE9EN,IAAY,QAAa,CAACT,EAAQ,IAAIS,CAAO,GAE7CyB,EAAahC,EAAMO,CAAO,GAE9Bf,EAAQ,OAAO,CAAE,KAAM,CAAE,KAAMC,EAAO,QAAQoB,CAAQ,CAAC,EAAI,UAAW,oBAAqB,KAAAb,CAAI,CAAE,CACnG,EAEA,MAAO,CACL,eAAeA,EAAI,CACjBN,EAAM,KAAKM,CAAI,CACjB,EACA,gBAAgBA,EAAI,CAClB,MAAMiC,EAAKC,EAAkBzC,EAAQO,CAAI,EAEzC,GAAIiC,IAAO,QAAajC,EAAK,WAAa,KAAM,OAEhD,MAAMqC,EAAS1C,EAAQ,IAAIsC,CAAE,GAAK,CAAA,EAElCI,EAAO,KAAKrC,EAAK,QAAQ,EACzBL,EAAQ,IAAIsC,EAAII,CAAM,CACxB,EACA,gBAAc,CACZ,UAAWR,KAAQnC,EAAOe,EAAeoB,CAAI,EAC7C,UAAWA,KAAQnC,EAAO,CACxB,MAAM4C,EAAeT,EAAK,UAAU,CAAC,EAEjCS,IAAiB,QAAaC,EAAO9C,EAAQoC,EAAK,MAAM,GAAGJ,EAAmBa,CAAY,EAC9FV,EAAgBC,CAAI,CACtB,CACA,UAAWA,KAAQnC,EAAO0C,EAAUP,CAAI,CAC1C,EAEJ,EACA,KAAM,CACJ,KAAM,CAAE,YAAa,8EAA8E,EACnG,SAAU,CACR,eACE,4GACF,kBACE,wJACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,yBACP,CAAA"}
@@ -1,7 +1,6 @@
1
+ type MessageIds = 'computedId' | 'missingCatalogId';
1
2
  type Options = readonly [{
2
3
  readonly allowInCallees: readonly string[];
3
4
  }];
4
- declare const requireLiteralId: import("@typescript-eslint/utils/ts-eslint").RuleModule<"computedId", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
- name: string;
6
- };
5
+ declare const requireLiteralId: import("../rule.js").RuleModule<MessageIds, Options>;
7
6
  export { requireLiteralId };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as i}from"@typescript-eslint/utils";import{calleeName as n,functionName as o}from"../ast.js";import{namedDeclarationOf as l}from"../declarations.js";import{createRule as s}from"../rule.js";import{staticValueOf as d}from"../static-value.js";function c(e){return e.type===i.CallExpression?n(e):o(e)}function m(e,a){let t=e.parent;for(;t!=null;){const r=c(t);if(r!==void 0&&a.includes(r))return!0;t=t.parent}return!1}const u=s({create(e,[{allowInCallees:a}]){return{CallExpression(t){const r=l(t);r===void 0||d(r.id,e.sourceCode.getScope(r.id)).kind!=="computed"||m(t,a)||e.report({data:{declaration:r.name},messageId:"computedId",node:r.id})}}},meta:{defaultOptions:[{allowInCallees:[]}],docs:{description:"A declaration names itself with a string that is written, not built at the call site."},messages:{computedId:"The id of {{declaration}} is built here, so nothing can find the declaration by its name. Write the id as a string, or name the factory that may compute it in `allowInCallees`."},schema:[{additionalProperties:!1,properties:{allowInCallees:{items:{type:"string"},type:"array"}},type:"object"}],type:"problem"},name:"require-literal-id"});export{u as requireLiteralId};
1
+ import{AST_NODE_TYPES as r}from"@typescript-eslint/types";import{calleeName as n,functionName as l}from"../ast.js";import{namedDeclarationOf as s}from"../declarations.js";import{createRule as o}from"../rule.js";import{staticValueOf as d}from"../static-value.js";function m(t){return t.type===r.CallExpression?n(t):l(t)}function c(t,a){let e=t.parent;for(;e!=null;){const i=m(e);if(i!==void 0&&a.includes(i))return!0;e=e.parent}return!1}const u=o({create(t,[{allowInCallees:a}]){return{CallExpression(e){if(n(e)==="defineLinkCatalog"&&e.arguments.length<2){t.report({messageId:"missingCatalogId",node:e});return}const i=s(e);i===void 0||d(i.id,t.sourceCode.getScope(i.id)).kind!=="computed"||c(e,a)||t.report({data:{declaration:i.name},messageId:"computedId",node:i.id})}}},meta:{defaultOptions:[{allowInCallees:[]}],docs:{description:"A declaration names itself with a string that is written, not built at the call site."},messages:{computedId:"The id of {{declaration}} is built here, so nothing can find the declaration by its name. Write the id as a string, or name the factory that may compute it in `allowInCallees`.",missingCatalogId:"defineLinkCatalog takes its id first and links second: defineLinkCatalog('app.links', links)."},schema:[{additionalProperties:!1,properties:{allowInCallees:{items:{type:"string"},type:"array"}},type:"object"}],type:"problem"},name:"require-literal-id"});export{u as requireLiteralId};
2
2
  //# sourceMappingURL=require-literal-id.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"require-literal-id.js","sources":["../../src/rules/require-literal-id.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName, functionName } from '../ast';\nimport { namedDeclarationOf } from '../declarations';\nimport { createRule } from '../rule';\nimport { staticValueOf } from '../static-value';\n\ntype MessageIds = 'computedId';\ntype Options = readonly [{ readonly allowInCallees: readonly string[] }];\n\n/** A factory names itself either as the function that writes the declaration or as the call that receives it. */\nfunction enclosingName(node: TSESTree.Node): string | undefined {\n if (node.type === AST_NODE_TYPES.CallExpression) return calleeName(node);\n\n return functionName(node);\n}\n\nfunction isInsideAllowedFactory(node: TSESTree.Node, allowed: readonly string[]): boolean {\n // The parent of `Program` is `null`, so the walk ends there rather than at an absent property.\n let current: TSESTree.Node | null | undefined = node.parent;\n\n while (current !== undefined && current !== null) {\n const name = enclosingName(current);\n\n if (name !== undefined && allowed.includes(name)) return true;\n\n current = current.parent;\n }\n\n return false;\n}\n\nconst requireLiteralId = createRule<Options, MessageIds>({\n create(context, [{ allowInCallees }]) {\n return {\n CallExpression(node): void {\n const declaration = namedDeclarationOf(node);\n\n if (declaration === undefined) return;\n\n const value = staticValueOf(declaration.id, context.sourceCode.getScope(declaration.id));\n\n if (value.kind !== 'computed' || isInsideAllowedFactory(node, allowInCallees)) return;\n\n context.report({\n data: { declaration: declaration.name },\n messageId: 'computedId',\n node: declaration.id,\n });\n },\n };\n },\n meta: {\n defaultOptions: [{ allowInCallees: [] }],\n docs: {\n description: 'A declaration names itself with a string that is written, not built at the call site.',\n },\n messages: {\n computedId:\n 'The id of {{declaration}} is built here, so nothing can find the declaration by its name. Write the id as a string, or name the factory that may compute it in `allowInCallees`.',\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n allowInCallees: { items: { type: 'string' }, type: 'array' },\n },\n type: 'object',\n },\n ],\n type: 'problem',\n },\n name: 'require-literal-id',\n});\n\nexport { requireLiteralId };\n"],"names":["enclosingName","node","AST_NODE_TYPES","calleeName","functionName","isInsideAllowedFactory","allowed","current","name","requireLiteralId","createRule","context","allowInCallees","declaration","namedDeclarationOf","staticValueOf"],"mappings":"sQAYA,SAASA,EAAcC,EAAmB,CACxC,OAAIA,EAAK,OAASC,EAAe,eAAuBC,EAAWF,CAAI,EAEhEG,EAAaH,CAAI,CAC1B,CAEA,SAASI,EAAuBJ,EAAqBK,EAA0B,CAE7E,IAAIC,EAA4CN,EAAK,OAErD,KAAgCM,GAAY,MAAM,CAChD,MAAMC,EAAOR,EAAcO,CAAO,EAElC,GAAIC,IAAS,QAAaF,EAAQ,SAASE,CAAI,EAAG,MAAO,GAEzDD,EAAUA,EAAQ,MACpB,CAEA,MAAO,EACT,CAEA,MAAME,EAAmBC,EAAgC,CACvD,OAAOC,EAAS,CAAC,CAAE,eAAAC,CAAc,CAAE,EAAC,CAClC,MAAO,CACL,eAAeX,EAAI,CACjB,MAAMY,EAAcC,EAAmBb,CAAI,EAEvCY,IAAgB,QAENE,EAAcF,EAAY,GAAIF,EAAQ,WAAW,SAASE,EAAY,EAAE,CAAC,EAE7E,OAAS,YAAcR,EAAuBJ,EAAMW,CAAc,GAE5ED,EAAQ,OAAO,CACb,KAAM,CAAE,YAAaE,EAAY,IAAI,EACrC,UAAW,aACX,KAAMA,EAAY,EACnB,CAAA,CACH,EAEJ,EACA,KAAM,CACJ,eAAgB,CAAC,CAAE,eAAgB,CAAA,EAAI,EACvC,KAAM,CACJ,YAAa,uFACd,EACD,SAAU,CACR,WACE,kLACH,EACD,OAAQ,CACN,CACE,qBAAsB,GACtB,WAAY,CACV,eAAgB,CAAE,MAAO,CAAE,KAAM,QAAQ,EAAI,KAAM,OAAO,CAC3D,EACD,KAAM,QACP,CACF,EACD,KAAM,SACP,EACD,KAAM,oBACP,CAAA"}
1
+ {"version":3,"file":"require-literal-id.js","sources":["../../src/rules/require-literal-id.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { calleeName, functionName } from '../ast';\nimport { namedDeclarationOf } from '../declarations';\nimport { createRule } from '../rule';\nimport { staticValueOf } from '../static-value';\n\ntype MessageIds = 'computedId' | 'missingCatalogId';\ntype Options = readonly [{ readonly allowInCallees: readonly string[] }];\n\n/** A factory names itself either as the function that writes the declaration or as the call that receives it. */\nfunction enclosingName(node: TSESTree.Node): string | undefined {\n if (node.type === AST_NODE_TYPES.CallExpression) return calleeName(node);\n\n return functionName(node);\n}\n\nfunction isInsideAllowedFactory(node: TSESTree.Node, allowed: readonly string[]): boolean {\n // The parent of `Program` is `null`, so the walk ends there rather than at an absent property.\n let current: TSESTree.Node | null | undefined = node.parent;\n\n while (current !== undefined && current !== null) {\n const name = enclosingName(current);\n\n if (name !== undefined && allowed.includes(name)) return true;\n\n current = current.parent;\n }\n\n return false;\n}\n\nconst requireLiteralId = createRule<Options, MessageIds>({\n create(context, [{ allowInCallees }]) {\n return {\n CallExpression(node): void {\n if (calleeName(node) === 'defineLinkCatalog' && node.arguments.length < 2) {\n context.report({ messageId: 'missingCatalogId', node });\n\n return;\n }\n\n const declaration = namedDeclarationOf(node);\n\n if (declaration === undefined) return;\n\n const value = staticValueOf(declaration.id, context.sourceCode.getScope(declaration.id));\n\n if (value.kind !== 'computed' || isInsideAllowedFactory(node, allowInCallees)) return;\n\n context.report({\n data: { declaration: declaration.name },\n messageId: 'computedId',\n node: declaration.id,\n });\n },\n };\n },\n meta: {\n defaultOptions: [{ allowInCallees: [] }],\n docs: {\n description: 'A declaration names itself with a string that is written, not built at the call site.',\n },\n messages: {\n computedId:\n 'The id of {{declaration}} is built here, so nothing can find the declaration by its name. Write the id as a string, or name the factory that may compute it in `allowInCallees`.',\n missingCatalogId: \"defineLinkCatalog takes its id first and links second: defineLinkCatalog('app.links', links).\",\n },\n schema: [\n {\n additionalProperties: false,\n properties: {\n allowInCallees: { items: { type: 'string' }, type: 'array' },\n },\n type: 'object',\n },\n ],\n type: 'problem',\n },\n name: 'require-literal-id',\n});\n\nexport { requireLiteralId };\n"],"names":["enclosingName","node","AST_NODE_TYPES","calleeName","functionName","isInsideAllowedFactory","allowed","current","name","requireLiteralId","createRule","context","allowInCallees","declaration","namedDeclarationOf","staticValueOf"],"mappings":"sQAYA,SAASA,EAAcC,EAAmB,CACxC,OAAIA,EAAK,OAASC,EAAe,eAAuBC,EAAWF,CAAI,EAEhEG,EAAaH,CAAI,CAC1B,CAEA,SAASI,EAAuBJ,EAAqBK,EAA0B,CAE7E,IAAIC,EAA4CN,EAAK,OAErD,KAAgCM,GAAY,MAAM,CAChD,MAAMC,EAAOR,EAAcO,CAAO,EAElC,GAAIC,IAAS,QAAaF,EAAQ,SAASE,CAAI,EAAG,MAAO,GAEzDD,EAAUA,EAAQ,MACpB,CAEA,MAAO,EACT,CAEA,MAAME,EAAmBC,EAAgC,CACvD,OAAOC,EAAS,CAAC,CAAE,eAAAC,CAAc,CAAE,EAAC,CAClC,MAAO,CACL,eAAeX,EAAI,CACjB,GAAIE,EAAWF,CAAI,IAAM,qBAAuBA,EAAK,UAAU,OAAS,EAAG,CACzEU,EAAQ,OAAO,CAAE,UAAW,mBAAoB,KAAAV,CAAI,CAAE,EAEtD,MACF,CAEA,MAAMY,EAAcC,EAAmBb,CAAI,EAEvCY,IAAgB,QAENE,EAAcF,EAAY,GAAIF,EAAQ,WAAW,SAASE,EAAY,EAAE,CAAC,EAE7E,OAAS,YAAcR,EAAuBJ,EAAMW,CAAc,GAE5ED,EAAQ,OAAO,CACb,KAAM,CAAE,YAAaE,EAAY,IAAI,EACrC,UAAW,aACX,KAAMA,EAAY,EACnB,CAAA,CACH,EAEJ,EACA,KAAM,CACJ,eAAgB,CAAC,CAAE,eAAgB,CAAA,EAAI,EACvC,KAAM,CACJ,YAAa,uFACd,EACD,SAAU,CACR,WACE,mLACF,iBAAkB,+FACnB,EACD,OAAQ,CACN,CACE,qBAAsB,GACtB,WAAY,CACV,eAAgB,CAAE,MAAO,CAAE,KAAM,QAAQ,EAAI,KAAM,OAAO,CAC3D,EACD,KAAM,QACP,CACF,EACD,KAAM,SACP,EACD,KAAM,oBACP,CAAA"}
@@ -0,0 +1,9 @@
1
+ type MessageIds = 'duplicateSection' | 'dynamicSection' | 'sectionsNotLiteral' | 'spreadSection' | 'unknownBodySection' | 'unknownSection';
2
+ /**
3
+ * D494: the sections of a feature are one record written where the declaration stands. The types read it in the order
4
+ * it is written — `exports` and `provides` see the `own` inferred above them (D477) — and every rule of this package
5
+ * that speaks about a feature reads that literal. A record built elsewhere, spread into it or keyed by a computed name
6
+ * hides from both what the feature declares, and a key written twice drops the first section without a word.
7
+ */
8
+ declare const requireStaticFeature: import("../rule.js").RuleModule<MessageIds, []>;
9
+ export { requireStaticFeature };
@@ -0,0 +1,2 @@
1
+ import{AST_NODE_TYPES as i}from"@typescript-eslint/types";import{propertyName as u}from"../ast.js";import{isBodyCall as f}from"../feature-body-records.js";import{isApi as h,unwrap as d}from"../model-bindings.js";import{createRule as p}from"../rule.js";const m={callee:"defineFeature",known:new Set(["imports","requires","own","exports","provides","when","body"]),unknown:"unknownSection"},w={callee:"defineFeature.body",known:new Set(["own","exports","provides"]),unknown:"unknownBodySection"},k=p({create(r){const s=r.sourceCode,c=(e,t,n)=>{if(e.type===i.SpreadElement){r.report({data:{callee:t.callee},messageId:"spreadSection",node:e});return}const o=u(e);if(o===void 0||e.kind!=="init"){r.report({data:{callee:t.callee},messageId:"dynamicSection",node:e.key});return}t.known.has(o)?n.has(o)&&r.report({data:{name:o},messageId:"duplicateSection",node:e.key}):r.report({data:{name:o},messageId:t.unknown,node:e.key}),n.add(o)},a=(e,t)=>{const n=new Set;for(const o of e.properties)c(o,t,n)},l=e=>{const[,t]=e.arguments,n=t===void 0?void 0:d(t);n?.type===i.ObjectExpression&&a(n,w)};return{CallExpression(e){if(f(s,e)){l(e);return}const[t,n]=e.arguments;if(n===void 0||t?.type===i.ObjectExpression||!h(s,e.callee,"@opetope/runtime","defineFeature"))return;const o=d(n);o.type===i.ObjectExpression?a(o,m):r.report({messageId:"sectionsNotLiteral",node:n})}}},meta:{docs:{description:"The sections of `defineFeature` are one object literal written at the call, with known keys only."},messages:{duplicateSection:"`{{name}}` is written twice in the sections of this feature, and the later one replaces the first without a word. Keep one (D494).",dynamicSection:"A section of `{{callee}}` is a plain key and its value: a computed key or an accessor hides from the types and from these rules which section the feature declares. Write the key as `own:`, `provides:` and the like (D494).",sectionsNotLiteral:"The sections of `defineFeature` are one object literal written at the call. The types infer `own` for `exports` and `provides` from that literal in the order it is written, and the rules of this package read it; a record built elsewhere hides both. Write the record inline (D477, D494).",spreadSection:"A spread in the sections of `{{callee}}` hides which sections it declares from the types and from these rules. Write each section in the record itself (D494).",unknownBodySection:"`{{name}}` is not a section of `defineFeature.body`, and the runtime refuses the body when it loads: a body takes the `own`, `exports` and `provides` of its header (D542, D494).",unknownSection:"`{{name}}` is not a section of `defineFeature`, and the runtime refuses the declaration: a feature takes `imports`, `requires`, `own`, `exports`, `provides`, `when` and `body` after its id (D280, D494)."},schema:[],type:"problem"},name:"require-static-feature"});export{k as requireStaticFeature};
2
+ //# sourceMappingURL=require-static-feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-static-feature.js","sources":["../../src/rules/require-static-feature.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { propertyName } from '../ast';\nimport { isBodyCall } from '../feature-body-records';\nimport { isApi, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds =\n | 'duplicateSection'\n | 'dynamicSection'\n | 'sectionsNotLiteral'\n | 'spreadSection'\n | 'unknownBodySection'\n | 'unknownSection';\n\n/** What one record of sections may hold, and how an unknown key of it is refused. */\ntype Sections = { readonly callee: string; readonly known: ReadonlySet<string>; readonly unknown: MessageIds };\n\n/** The sections `defineFeature` takes after its id, in the order `define-feature-property-order` holds (D542, D280). */\nconst FEATURE: Sections = {\n callee: 'defineFeature',\n known: new Set(['imports', 'requires', 'own', 'exports', 'provides', 'when', 'body']),\n unknown: 'unknownSection',\n};\n\n/** The sections of a body file: `own`, `exports` and `provides` of the header it implements (D542). */\nconst BODY: Sections = {\n callee: 'defineFeature.body',\n known: new Set(['own', 'exports', 'provides']),\n unknown: 'unknownBodySection',\n};\n\n/**\n * D494: the sections of a feature are one record written where the declaration stands. The types read it in the order\n * it is written — `exports` and `provides` see the `own` inferred above them (D477) — and every rule of this package\n * that speaks about a feature reads that literal. A record built elsewhere, spread into it or keyed by a computed name\n * hides from both what the feature declares, and a key written twice drops the first section without a word.\n */\nconst requireStaticFeature = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n\n const checkProperty = (property: TSESTree.ObjectLiteralElement, sections: Sections, seen: Set<string>): void => {\n if (property.type === AST_NODE_TYPES.SpreadElement) {\n context.report({ data: { callee: sections.callee }, messageId: 'spreadSection', node: property });\n\n return;\n }\n\n const name = propertyName(property);\n\n if (name === undefined || property.kind !== 'init') {\n context.report({ data: { callee: sections.callee }, messageId: 'dynamicSection', node: property.key });\n\n return;\n }\n\n if (!sections.known.has(name)) {\n context.report({ data: { name }, messageId: sections.unknown, node: property.key });\n } else if (seen.has(name)) {\n context.report({ data: { name }, messageId: 'duplicateSection', node: property.key });\n }\n\n seen.add(name);\n };\n\n const checkRecord = (record: TSESTree.ObjectExpression, sections: Sections): void => {\n const seen = new Set<string>();\n\n for (const property of record.properties) checkProperty(property, sections, seen);\n };\n\n /** A body file may hold its record in a name, as the feature reference shows; a record written here is read. */\n const checkBody = (node: TSESTree.CallExpression): void => {\n const [, implementation] = node.arguments;\n const record = implementation === undefined ? undefined : unwrap(implementation);\n\n if (record?.type === AST_NODE_TYPES.ObjectExpression) checkRecord(record, BODY);\n };\n\n return {\n CallExpression(node): void {\n if (isBodyCall(source, node)) {\n checkBody(node);\n\n return;\n }\n\n const [identity, sections] = node.arguments;\n\n // The single-record form D280 retired is refused by the compiler; this rule reads the current one.\n if (sections === undefined || identity?.type === AST_NODE_TYPES.ObjectExpression) return;\n\n if (!isApi(source, node.callee, '@opetope/runtime', 'defineFeature')) return;\n\n // `satisfies` and `as const` are for the types alone: the literal under them is the record.\n const record = unwrap(sections);\n\n if (record.type === AST_NODE_TYPES.ObjectExpression) {\n checkRecord(record, FEATURE);\n } else {\n context.report({ messageId: 'sectionsNotLiteral', node: sections });\n }\n },\n };\n },\n meta: {\n docs: {\n description: 'The sections of `defineFeature` are one object literal written at the call, with known keys only.',\n },\n messages: {\n duplicateSection:\n '`{{name}}` is written twice in the sections of this feature, and the later one replaces the first without a word. Keep one (D494).',\n dynamicSection:\n 'A section of `{{callee}}` is a plain key and its value: a computed key or an accessor hides from the types and from these rules which section the feature declares. Write the key as `own:`, `provides:` and the like (D494).',\n sectionsNotLiteral:\n 'The sections of `defineFeature` are one object literal written at the call. The types infer `own` for `exports` and `provides` from that literal in the order it is written, and the rules of this package read it; a record built elsewhere hides both. Write the record inline (D477, D494).',\n spreadSection:\n 'A spread in the sections of `{{callee}}` hides which sections it declares from the types and from these rules. Write each section in the record itself (D494).',\n unknownBodySection:\n '`{{name}}` is not a section of `defineFeature.body`, and the runtime refuses the body when it loads: a body takes the `own`, `exports` and `provides` of its header (D542, D494).',\n unknownSection:\n '`{{name}}` is not a section of `defineFeature`, and the runtime refuses the declaration: a feature takes `imports`, `requires`, `own`, `exports`, `provides`, `when` and `body` after its id (D280, D494).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'require-static-feature',\n});\n\nexport { requireStaticFeature };\n"],"names":["FEATURE","BODY","requireStaticFeature","createRule","context","source","checkProperty","property","sections","seen","AST_NODE_TYPES","name","propertyName","checkRecord","record","checkBody","node","implementation","unwrap","isBodyCall","identity","isApi"],"mappings":"4PAoBA,MAAMA,EAAoB,CACxB,OAAQ,gBACR,MAAO,IAAI,IAAI,CAAC,UAAW,WAAY,MAAO,UAAW,WAAY,OAAQ,MAAM,CAAC,EACpF,QAAS,kBAILC,EAAiB,CACrB,OAAQ,qBACR,MAAO,IAAI,IAAI,CAAC,MAAO,UAAW,UAAU,CAAC,EAC7C,QAAS,sBASLC,EAAuBC,EAA2B,CACtD,OAAOC,EAAO,CACZ,MAAMC,EAASD,EAAQ,WAEjBE,EAAgB,CAACC,EAAyCC,EAAoBC,IAA2B,CAC7G,GAAIF,EAAS,OAASG,EAAe,cAAe,CAClDN,EAAQ,OAAO,CAAE,KAAM,CAAE,OAAQI,EAAS,MAAM,EAAI,UAAW,gBAAiB,KAAMD,CAAQ,CAAE,EAEhG,MACF,CAEA,MAAMI,EAAOC,EAAaL,CAAQ,EAElC,GAAII,IAAS,QAAaJ,EAAS,OAAS,OAAQ,CAClDH,EAAQ,OAAO,CAAE,KAAM,CAAE,OAAQI,EAAS,MAAM,EAAI,UAAW,iBAAkB,KAAMD,EAAS,GAAG,CAAE,EAErG,MACF,CAEKC,EAAS,MAAM,IAAIG,CAAI,EAEjBF,EAAK,IAAIE,CAAI,GACtBP,EAAQ,OAAO,CAAE,KAAM,CAAE,KAAAO,CAAI,EAAI,UAAW,mBAAoB,KAAMJ,EAAS,GAAG,CAAE,EAFpFH,EAAQ,OAAO,CAAE,KAAM,CAAE,KAAAO,CAAI,EAAI,UAAWH,EAAS,QAAS,KAAMD,EAAS,GAAG,CAAE,EAKpFE,EAAK,IAAIE,CAAI,CACf,EAEME,EAAc,CAACC,EAAmCN,IAA4B,CAClF,MAAMC,EAAO,IAAI,IAEjB,UAAWF,KAAYO,EAAO,WAAYR,EAAcC,EAAUC,EAAUC,CAAI,CAClF,EAGMM,EAAaC,GAAuC,CACxD,KAAM,CAAA,CAAGC,CAAc,EAAID,EAAK,UAC1BF,EAASG,IAAmB,OAAY,OAAYC,EAAOD,CAAc,EAE3EH,GAAQ,OAASJ,EAAe,kBAAkBG,EAAYC,EAAQb,CAAI,CAChF,EAEA,MAAO,CACL,eAAee,EAAI,CACjB,GAAIG,EAAWd,EAAQW,CAAI,EAAG,CAC5BD,EAAUC,CAAI,EAEd,MACF,CAEA,KAAM,CAACI,EAAUZ,CAAQ,EAAIQ,EAAK,UAKlC,GAFIR,IAAa,QAAaY,GAAU,OAASV,EAAe,kBAE5D,CAACW,EAAMhB,EAAQW,EAAK,OAAQ,mBAAoB,eAAe,EAAG,OAGtE,MAAMF,EAASI,EAAOV,CAAQ,EAE1BM,EAAO,OAASJ,EAAe,iBACjCG,EAAYC,EAAQd,CAAO,EAE3BI,EAAQ,OAAO,CAAE,UAAW,qBAAsB,KAAMI,EAAU,CAEtE,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,mGACd,EACD,SAAU,CACR,iBACE,qIACF,eACE,gOACF,mBACE,iSACF,cACE,iKACF,mBACE,oLACF,eACE,4MACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,wBACP,CAAA"}
@@ -0,0 +1,10 @@
1
+ type MessageIds = 'unreleasedSubscription' | 'unreleasedViewSubscription';
2
+ /**
3
+ * D470: a model scope builder returns a fields record, and a view builder a Readable — never a disposer its node would
4
+ * run — so a subscription its own body opens by hand is released only through the child context's `cleanup`. This
5
+ * holds in every layer — the model layer included, where `no-subscribe-outside-models` steps aside — because the leak
6
+ * is the builder's, not the layer's. A builder is the function at the builder's position of the call, inline or bound
7
+ * to a name in the same file.
8
+ */
9
+ declare const scopeBuilderReleasesSubscriptions: import("../rule.js").RuleModule<MessageIds, []>;
10
+ export { scopeBuilderReleasesSubscriptions };
@@ -0,0 +1,2 @@
1
+ import{isSubscribeCall as i,enclosingBuilder as o,isDeclaredIngress as c,isReleasedByCleanup as l}from"../declaration-ingress.js";import{createRule as n}from"../rule.js";const a=n({create(s){return{CallExpression(e){if(!i(e))return;const r=o(s.sourceCode,e);r===void 0||c(s.sourceCode,e)||l(s.sourceCode,e,r.fn)||s.report({messageId:r.method==="resource.view"?"unreleasedViewSubscription":"unreleasedSubscription",node:e.callee})}}},meta:{docs:{description:"A subscription a model scope builder opens by hand hands its release to `child.cleanup`."},messages:{unreleasedSubscription:"A scope builder returns fields, not a disposer, so nothing releases this subscription. Hand its release to `child.cleanup` \u2014 `child.cleanup(source.subscribe(listener))`, or a name `child.cleanup` receives or calls \u2014 or declare it as `child.event` or the `connect` of `child.resource.live`.",unreleasedViewSubscription:"A resource.view builder returns a Readable, not a disposer, so nothing releases this subscription. Hand its release to `scope.cleanup` \u2014 `scope.cleanup(source.subscribe(listener))`, or a name `scope.cleanup` receives or calls \u2014 or declare it as `scope.event` or the `connect` of `scope.resource.live`."},schema:[],type:"problem"},name:"scope-builder-releases-subscriptions"});export{a as scopeBuilderReleasesSubscriptions};
2
+ //# sourceMappingURL=scope-builder-releases-subscriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-builder-releases-subscriptions.js","sources":["../../src/rules/scope-builder-releases-subscriptions.ts"],"sourcesContent":["import { enclosingBuilder, isDeclaredIngress, isReleasedByCleanup, isSubscribeCall } from '../declaration-ingress';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'unreleasedSubscription' | 'unreleasedViewSubscription';\n\n/**\n * D470: a model scope builder returns a fields record, and a view builder a Readable — never a disposer its node would\n * run — so a subscription its own body opens by hand is released only through the child context's `cleanup`. This\n * holds in every layer — the model layer included, where `no-subscribe-outside-models` steps aside — because the leak\n * is the builder's, not the layer's. A builder is the function at the builder's position of the call, inline or bound\n * to a name in the same file.\n */\nconst scopeBuilderReleasesSubscriptions = createRule<[], MessageIds>({\n create(context) {\n return {\n CallExpression(node): void {\n if (!isSubscribeCall(node)) return;\n\n const builder = enclosingBuilder(context.sourceCode, node);\n\n if (\n builder === undefined ||\n isDeclaredIngress(context.sourceCode, node) ||\n isReleasedByCleanup(context.sourceCode, node, builder.fn)\n ) {\n return;\n }\n\n context.report({\n messageId: builder.method === 'resource.view' ? 'unreleasedViewSubscription' : 'unreleasedSubscription',\n node: node.callee,\n });\n },\n };\n },\n meta: {\n docs: {\n description: 'A subscription a model scope builder opens by hand hands its release to `child.cleanup`.',\n },\n messages: {\n unreleasedSubscription:\n 'A scope builder returns fields, not a disposer, so nothing releases this subscription. Hand its release to `child.cleanup` — `child.cleanup(source.subscribe(listener))`, or a name `child.cleanup` receives or calls — or declare it as `child.event` or the `connect` of `child.resource.live`.',\n unreleasedViewSubscription:\n 'A resource.view builder returns a Readable, not a disposer, so nothing releases this subscription. Hand its release to `scope.cleanup` — `scope.cleanup(source.subscribe(listener))`, or a name `scope.cleanup` receives or calls — or declare it as `scope.event` or the `connect` of `scope.resource.live`.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'scope-builder-releases-subscriptions',\n});\n\nexport { scopeBuilderReleasesSubscriptions };\n"],"names":["scopeBuilderReleasesSubscriptions","createRule","context","node","isSubscribeCall","builder","enclosingBuilder","isDeclaredIngress","isReleasedByCleanup"],"mappings":"0KAYA,MAAMA,EAAoCC,EAA2B,CACnE,OAAOC,EAAO,CACZ,MAAO,CACL,eAAeC,EAAI,CACjB,GAAI,CAACC,EAAgBD,CAAI,EAAG,OAE5B,MAAME,EAAUC,EAAiBJ,EAAQ,WAAYC,CAAI,EAGvDE,IAAY,QACZE,EAAkBL,EAAQ,WAAYC,CAAI,GAC1CK,EAAoBN,EAAQ,WAAYC,EAAME,EAAQ,EAAE,GAK1DH,EAAQ,OAAO,CACb,UAAWG,EAAQ,SAAW,gBAAkB,6BAA+B,yBAC/E,KAAMF,EAAK,MACZ,CAAA,CACH,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,0FACd,EACD,SAAU,CACR,uBACE,8SACF,2BACE,yTACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,sCACP,CAAA"}
@@ -1,4 +1,5 @@
1
- import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
1
+ import type { TSESTree } from '@typescript-eslint/types';
2
+ import type { SourceCode } from './rule.js';
2
3
  /** What one file wrote, collected before any write is judged: a rule answers on `Program:exit` (D290). */
3
4
  type Written = {
4
5
  readonly barriers: TSESTree.Node[];
@@ -32,8 +33,8 @@ type Edit = {
32
33
  readonly text: string;
33
34
  };
34
35
  /** The write this rule reports, or nothing: a value that reads another readable replaces no state of its own. */
35
- declare function siteAt(source: TSESLint.SourceCode, node: TSESTree.CallExpression, written: Written): Site | undefined;
36
+ declare function siteAt(source: SourceCode, node: TSESTree.CallExpression, written: Written): Site | undefined;
36
37
  /** One rewrite, whether it is offered as a fix or as a suggestion: the value, and the names it leaves unused. */
37
- declare function editsOf(source: TSESLint.SourceCode, site: Site, name: string): readonly Edit[];
38
+ declare function editsOf(source: SourceCode, site: Site, name: string): readonly Edit[];
38
39
  export type { Site, Written };
39
40
  export { editsOf, siteAt };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as s,AST_TOKEN_TYPES as g}from"@typescript-eslint/utils";import{isFunctionLike as T,propertyName as I}from"./ast.js";import{enclosingFunction as y,unwrap as l,variableOf as m}from"./model-bindings.js";const A=["previous","snapshot"],S=2;function R(e){return e.type===s.Identifier?e.name==="update":e.type!==s.MemberExpression||e.computed?!1:e.property.type===s.Identifier&&e.property.name==="update"}function O(e){if(e===void 0)return;const n=l(e);return n.type===s.Identifier||n.type===s.MemberExpression?n:void 0}function $(e){if(!R(e.callee)||e.arguments.length!==S)return;const[n,t]=e.arguments,r=O(n);if(!(r===void 0||t===void 0||t.type===s.SpreadElement))return T(l(t))?void 0:{state:r,value:t}}function h(e){const{callee:n}=e;if(!(n.type!==s.MemberExpression||n.computed)&&!(n.property.type!==s.Identifier||n.property.name!=="getSnapshot"))return e.arguments.length===0?l(n.object):void 0}function v(e,n,t){if(n.type!==s.Identifier)return t.type===s.MemberExpression&&e.getText(t)===e.getText(n);if(t.type!==s.Identifier)return!1;const r=m(e,n),i=m(e,t);return r===null&&i===null?n.name===t.name:r===i}function a(e,n){return e.range[0]>=n.range[0]&&e.range[1]<=n.range[1]}function k(e,n,t){return t.functions.some(r=>a(r,n)&&a(e,r))}function w(e,n,t,r){const i=[];for(const u of r.calls){if(!a(u,t))continue;const f=h(u);f!==void 0&&v(e,n,f)&&i.push(u)}return i}function M(e){const n=[];for(const t of e.properties){if(t.type!==s.Property||t.value.type!==s.Identifier)return;const r=I(t);if(r===void 0)return;n.push({id:t.value,key:r})}return n}function N(e){return e.type===s.Identifier?[{id:e,key:void 0}]:e.type===s.ObjectPattern?M(e):void 0}function C(e){const n=N(e.id);if(n===void 0||e.init===null)return;const t=l(e.init),r=t.type===s.CallExpression?h(t):void 0;return r===void 0?void 0:{bindings:n,receiver:r}}function P(e){const{parent:n}=e;return n===void 0?!1:n.type===s.AssignmentExpression?n.left===e:n.type===s.UpdateExpression}function F(e,n){return e.references.some(({identifier:t})=>t!==n.id&&P(t))}function _(e,n){return!a(e,n)&&!a(n,e)}function Y(e,n,t,r){return t.key===void 0&&n.kind==="const"&&r===0&&n.declarations.length===1&&_(e,n)}function j(e,n,t,r){const i=[];let u=0;for(const{identifier:o}of r.references)o!==t.id&&(a(o,e)?i.push({key:t.key,node:o}):u+=1);const f=n.parent;if(i.length!==0&&!(f.kind!=="const"&&F(r,t)))return{declaration:f,references:i,removable:Y(e,f,t,u)}}function L(e,n,t,r,i){const u=[];for(const f of r){if(y(e,f.id)!==i)continue;const o=m(e,f.id),d=o===null?void 0:j(n,t,f,o);d!==void 0&&u.push(d)}return u}function U(e,n,t,r,i){const u=[];for(const f of i.declarators){const o=C(f);o===void 0||!v(e,n,o.receiver)||u.push(...L(e,t,f,o.bindings,r))}return u}function q(e,n){const t=new Set(e.getTokens(n).map(r=>r.value));return A.filter(r=>!t.has(r))}function z(e,n,t){let r=e.getScope(n);for(;r!==null;){if(r.variables.some(i=>i.name===t))return!1;r=r.upper}return!0}function B(e,n,t){const r=q(e,t),i=r.find(u=>z(e,n,u));return{free:i!==void 0,name:i??r[0]}}function D(e,n,t){return t.barriers.some(r=>a(r,n)&&!k(r,n,t))?!1:!e.some(r=>k(r.node,n,t))}function K(e,n,t){const r=$(n);if(r===void 0)return;const{state:i,value:u}=r,f=w(e,i,u,t).map(c=>({key:void 0,node:c})),o=U(e,i,u,y(e,n),t),d=o.flatMap(c=>c.references),p=[...f,...d].sort((c,E)=>c.node.range[0]-E.node.range[0]);if(p.length===0)return;const{free:b,name:x}=B(e,n,u);return{aliases:o,free:b,name:x,reads:p,rewritable:D(p,u,t),value:u}}function W(e,n){return e.getFirstToken(n)?.value==="{"||n.type===s.SequenceExpression}function G(e,n,t){const r=n.key===void 0?t:`${t}.${n.key}`,{parent:i}=n.node;return i?.type!==s.Property||!i.shorthand||i.value!==n.node?r:`${e.getText(i.key)}: ${r}`}function H(e,n,t){const r=n.value.range[0],i=e.getText(n.value);let u="",f=0;for(const o of n.reads)u+=i.slice(f,o.node.range[0]-r)+G(e,o,t),f=o.node.range[1]-r;return u+=i.slice(f),`${t} => ${W(e,n.value)?`(${u})`:u}`}function J(e,n,t){return!(t.type===g.Line||t.type===g.Block)||t.loc.start.line!==n.loc.end.line?t:e.getTokenAfter(t,{includeComments:!0})}function Q(e,n){const t=e.getTokenAfter(n,{includeComments:!0}),r=t===null?null:J(e,n,t);return r===null?n.range:[n.range[0],r.range[0]]}function V(e,n,t){const r=n.aliases.filter(i=>i.removable).map(i=>({range:Q(e,i.declaration),text:""}));return[{range:n.value.range,text:H(e,n,t)},...r]}export{V as editsOf,K as siteAt};
1
+ import{AST_NODE_TYPES as s,AST_TOKEN_TYPES as g}from"@typescript-eslint/types";import{isFunctionLike as T,propertyName as I}from"./ast.js";import{enclosingFunction as y,unwrap as l,variableOf as m}from"./model-bindings.js";const A=["previous","snapshot"],S=2;function R(e){return e.type===s.Identifier?e.name==="update":e.type!==s.MemberExpression||e.computed?!1:e.property.type===s.Identifier&&e.property.name==="update"}function O(e){if(e===void 0)return;const n=l(e);return n.type===s.Identifier||n.type===s.MemberExpression?n:void 0}function $(e){if(!R(e.callee)||e.arguments.length!==S)return;const[n,t]=e.arguments,r=O(n);if(!(r===void 0||t===void 0||t.type===s.SpreadElement))return T(l(t))?void 0:{state:r,value:t}}function h(e){const{callee:n}=e;if(!(n.type!==s.MemberExpression||n.computed)&&!(n.property.type!==s.Identifier||n.property.name!=="getSnapshot"))return e.arguments.length===0?l(n.object):void 0}function v(e,n,t){if(n.type!==s.Identifier)return t.type===s.MemberExpression&&e.getText(t)===e.getText(n);if(t.type!==s.Identifier)return!1;const r=m(e,n),i=m(e,t);return r===null&&i===null?n.name===t.name:r===i}function a(e,n){return e.range[0]>=n.range[0]&&e.range[1]<=n.range[1]}function k(e,n,t){return t.functions.some(r=>a(r,n)&&a(e,r))}function w(e,n,t,r){const i=[];for(const u of r.calls){if(!a(u,t))continue;const f=h(u);f!==void 0&&v(e,n,f)&&i.push(u)}return i}function M(e){const n=[];for(const t of e.properties){if(t.type!==s.Property||t.value.type!==s.Identifier)return;const r=I(t);if(r===void 0)return;n.push({id:t.value,key:r})}return n}function N(e){return e.type===s.Identifier?[{id:e,key:void 0}]:e.type===s.ObjectPattern?M(e):void 0}function C(e){const n=N(e.id);if(n===void 0||e.init===null)return;const t=l(e.init),r=t.type===s.CallExpression?h(t):void 0;return r===void 0?void 0:{bindings:n,receiver:r}}function P(e){const{parent:n}=e;return n===void 0?!1:n.type===s.AssignmentExpression?n.left===e:n.type===s.UpdateExpression}function F(e,n){return e.references.some(({identifier:t})=>t!==n.id&&P(t))}function _(e,n){return!a(e,n)&&!a(n,e)}function Y(e,n,t,r){return t.key===void 0&&n.kind==="const"&&r===0&&n.declarations.length===1&&_(e,n)}function j(e,n,t,r){const i=[];let u=0;for(const{identifier:o}of r.references)o!==t.id&&(a(o,e)?i.push({key:t.key,node:o}):u+=1);const f=n.parent;if(i.length!==0&&!(f.kind!=="const"&&F(r,t)))return{declaration:f,references:i,removable:Y(e,f,t,u)}}function L(e,n,t,r,i){const u=[];for(const f of r){if(y(e,f.id)!==i)continue;const o=m(e,f.id),d=o===null?void 0:j(n,t,f,o);d!==void 0&&u.push(d)}return u}function U(e,n,t,r,i){const u=[];for(const f of i.declarators){const o=C(f);o===void 0||!v(e,n,o.receiver)||u.push(...L(e,t,f,o.bindings,r))}return u}function q(e,n){const t=new Set(e.getTokens(n).map(r=>r.value));return A.filter(r=>!t.has(r))}function z(e,n,t){let r=e.getScope(n);for(;r!==null;){if(r.variables.some(i=>i.name===t))return!1;r=r.upper}return!0}function B(e,n,t){const r=q(e,t),i=r.find(u=>z(e,n,u));return{free:i!==void 0,name:i??r[0]}}function D(e,n,t){return t.barriers.some(r=>a(r,n)&&!k(r,n,t))?!1:!e.some(r=>k(r.node,n,t))}function K(e,n,t){const r=$(n);if(r===void 0)return;const{state:i,value:u}=r,f=w(e,i,u,t).map(c=>({key:void 0,node:c})),o=U(e,i,u,y(e,n),t),d=o.flatMap(c=>c.references),p=[...f,...d].sort((c,E)=>c.node.range[0]-E.node.range[0]);if(p.length===0)return;const{free:b,name:x}=B(e,n,u);return{aliases:o,free:b,name:x,reads:p,rewritable:D(p,u,t),value:u}}function W(e,n){return e.getFirstToken(n)?.value==="{"||n.type===s.SequenceExpression}function G(e,n,t){const r=n.key===void 0?t:`${t}.${n.key}`,{parent:i}=n.node;return i?.type!==s.Property||!i.shorthand||i.value!==n.node?r:`${e.getText(i.key)}: ${r}`}function H(e,n,t){const r=n.value.range[0],i=e.getText(n.value);let u="",f=0;for(const o of n.reads)u+=i.slice(f,o.node.range[0]-r)+G(e,o,t),f=o.node.range[1]-r;return u+=i.slice(f),`${t} => ${W(e,n.value)?`(${u})`:u}`}function J(e,n,t){return!(t.type===g.Line||t.type===g.Block)||t.loc.start.line!==n.loc.end.line?t:e.getTokenAfter(t,{includeComments:!0})}function Q(e,n){const t=e.getTokenAfter(n,{includeComments:!0}),r=t===null?null:J(e,n,t);return r===null?n.range:[n.range[0],r.range[0]]}function V(e,n,t){const r=n.aliases.filter(i=>i.removable).map(i=>({range:Q(e,i.declaration),text:""}));return[{range:n.value.range,text:H(e,n,t)},...r]}export{V as editsOf,K as siteAt};
2
2
  //# sourceMappingURL=snapshot-updates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-updates.js","sources":["../src/snapshot-updates.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';\n\nimport { isFunctionLike, propertyName } from './ast';\nimport { enclosingFunction, unwrap, variableOf } from './model-bindings';\n\n/** What one file wrote, collected before any write is judged: a rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly barriers: TSESTree.Node[];\n readonly calls: TSESTree.CallExpression[];\n readonly declarators: TSESTree.VariableDeclarator[];\n readonly functions: TSESTree.Node[];\n};\n\n/** One node the rewrite replaces: the parameter goes where the whole read was, a field of it where a field was. */\ntype Read = { readonly key: string | undefined; readonly node: TSESTree.Node };\n\n/** What a declaration gave the read: the value under one name, or fields of it under names of their own. */\ntype Bound = { readonly id: TSESTree.Identifier; readonly key: string | undefined };\n\n/** A name given to the read before the write: the references inside the value, and whether the name has no others. */\ntype Alias = {\n readonly declaration: TSESTree.VariableDeclaration;\n readonly references: readonly Read[];\n readonly removable: boolean;\n};\n\n/** One `update(state, value)` whose value reads the state it replaces, with everything a rewrite of it needs. */\ntype Site = {\n readonly aliases: readonly Alias[];\n readonly free: boolean;\n readonly name: string | undefined;\n readonly reads: readonly Read[];\n readonly rewritable: boolean;\n readonly value: TSESTree.Node;\n};\n\n/** One replacement the rewrite writes, so the rule hands the fixer ranges and never a node of its own. */\ntype Edit = { readonly range: TSESTree.Range; readonly text: string };\n\n/** The parameter the updater is written with, in the order a reader expects to meet it. */\nconst NAMES = ['previous', 'snapshot'];\nconst ARITY = 2;\n\n/** `update(state, value)` and `ctx.update(state, value)` alike: the writer is called by that name (D288). */\nfunction isUpdateCallee(node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) return node.name === 'update';\n\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n return node.property.type === AST_NODE_TYPES.Identifier && node.property.name === 'update';\n}\n\n/** The readable a write names first, where it is named at all: a call in that position is somebody else's value. */\nfunction stateArgument(node: TSESTree.Node | undefined): TSESTree.Node | undefined {\n if (node === undefined) return undefined;\n\n const state = unwrap(node);\n\n return state.type === AST_NODE_TYPES.Identifier || state.type === AST_NODE_TYPES.MemberExpression ? state : undefined;\n}\n\n/**\n * The shape this rule reads: two arguments, a readable named first, and a value that is not already an updater.\n * A spread in the value position is not the value — the arguments are assembled elsewhere, and what arrives in\n * this position is not written here.\n */\nfunction writeAt(node: TSESTree.CallExpression): { state: TSESTree.Node; value: TSESTree.Node } | undefined {\n if (!isUpdateCallee(node.callee) || node.arguments.length !== ARITY) return undefined;\n\n const [first, second] = node.arguments;\n const state = stateArgument(first);\n\n if (state === undefined || second === undefined || second.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n return isFunctionLike(unwrap(second)) ? undefined : { state, value: second };\n}\n\n/** `s.getSnapshot()`, and the receiver it was read on; a reference that is not called reads nothing. */\nfunction snapshotReceiver(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const { callee } = node;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return undefined;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return undefined;\n\n return node.arguments.length === 0 ? unwrap(callee.object) : undefined;\n}\n\n/**\n * The same readable: a name is the binding it resolves to, and a member expression is the path it spells. Two\n * different objects written the same way are one receiver here, which is what reading shapes rather than types buys.\n */\nfunction sameReceiver(source: TSESLint.SourceCode, state: TSESTree.Node, candidate: TSESTree.Node): boolean {\n if (state.type !== AST_NODE_TYPES.Identifier) {\n return candidate.type === AST_NODE_TYPES.MemberExpression && source.getText(candidate) === source.getText(state);\n }\n\n if (candidate.type !== AST_NODE_TYPES.Identifier) return false;\n\n const named = variableOf(source, state);\n const read = variableOf(source, candidate);\n\n return named === null && read === null ? state.name === candidate.name : named === read;\n}\n\nfunction within(node: TSESTree.Node, outer: TSESTree.Node): boolean {\n return node.range[0] >= outer.range[0] && node.range[1] <= outer.range[1];\n}\n\n/** A function written inside the value runs later than the value does, so what it holds does not travel with it. */\nfunction nested(node: TSESTree.Node, value: TSESTree.Node, written: Written): boolean {\n return written.functions.some(fn => within(fn, value) && within(node, fn));\n}\n\n/** Every read of this state written into the value itself. */\nfunction snapshotsIn(\n source: TSESLint.SourceCode,\n state: TSESTree.Node,\n value: TSESTree.Node,\n written: Written,\n): readonly TSESTree.CallExpression[] {\n const reads: TSESTree.CallExpression[] = [];\n\n for (const call of written.calls) {\n if (!within(call, value)) continue;\n\n const receiver = snapshotReceiver(call);\n\n if (receiver !== undefined && sameReceiver(source, state, receiver)) reads.push(call);\n }\n\n return reads;\n}\n\n/** `const { items } = s.getSnapshot()`: a field taken apart is still that field of the read, under a name. */\nfunction patternNames(pattern: TSESTree.ObjectPattern): readonly Bound[] | undefined {\n const names: Bound[] = [];\n\n for (const property of pattern.properties) {\n if (property.type !== AST_NODE_TYPES.Property || property.value.type !== AST_NODE_TYPES.Identifier)\n return undefined;\n\n const key = propertyName(property);\n\n if (key === undefined) return undefined;\n\n names.push({ id: property.value, key });\n }\n\n return names;\n}\n\n/** Every name a declaration gave the read; a shape this rule cannot write back through gives none. */\nfunction boundNames(id: TSESTree.Node): readonly Bound[] | undefined {\n if (id.type === AST_NODE_TYPES.Identifier) return [{ id, key: undefined }];\n\n return id.type === AST_NODE_TYPES.ObjectPattern ? patternNames(id) : undefined;\n}\n\n/** `const current = s.getSnapshot()`: the names it binds and the receiver it read, where it is written that plainly. */\nfunction namedRead(\n declarator: TSESTree.VariableDeclarator,\n): { bindings: readonly Bound[]; receiver: TSESTree.Node } | undefined {\n const bindings = boundNames(declarator.id);\n\n if (bindings === undefined || declarator.init === null) return undefined;\n\n const init = unwrap(declarator.init);\n const receiver = init.type === AST_NODE_TYPES.CallExpression ? snapshotReceiver(init) : undefined;\n\n return receiver === undefined ? undefined : { bindings, receiver };\n}\n\n/** A name written again does not hold the read it was opened with, so the rewrite has nothing to say about it. */\nfunction isWritten(node: TSESTree.Node): boolean {\n const { parent } = node;\n\n if (parent === undefined) return false;\n\n if (parent.type === AST_NODE_TYPES.AssignmentExpression) return parent.left === node;\n\n return parent.type === AST_NODE_TYPES.UpdateExpression;\n}\n\nfunction isReassigned(variable: TSESLint.Scope.Variable, binding: Bound): boolean {\n return variable.references.some(({ identifier }) => identifier !== binding.id && isWritten(identifier));\n}\n\n/** Code where the two overlap is broken before this rule reads it, and a rewrite of it must not overlap either. */\nfunction separate(value: TSESTree.Node, declaration: TSESTree.Node): boolean {\n return !within(value, declaration) && !within(declaration, value);\n}\n\n/** Only a `const` that held the whole read, for this value and nobody else, leaves the file with it. */\nfunction isRemovable(\n value: TSESTree.Node,\n declaration: TSESTree.VariableDeclaration,\n binding: Bound,\n elsewhere: number,\n): boolean {\n return (\n binding.key === undefined &&\n declaration.kind === 'const' &&\n elsewhere === 0 &&\n declaration.declarations.length === 1 &&\n separate(value, declaration)\n );\n}\n\n/** The references this value makes of one name, and whether the declaration behind it goes with them. */\nfunction aliasOf(\n value: TSESTree.Node,\n declarator: TSESTree.VariableDeclarator,\n binding: Bound,\n variable: TSESLint.Scope.Variable,\n): Alias | undefined {\n const references: Read[] = [];\n let elsewhere = 0;\n\n for (const { identifier } of variable.references) {\n if (identifier === binding.id) continue;\n\n if (within(identifier, value)) references.push({ key: binding.key, node: identifier });\n else elsewhere += 1;\n }\n\n const declaration = declarator.parent;\n\n if (references.length === 0) return undefined;\n if (declaration.kind !== 'const' && isReassigned(variable, binding)) return undefined;\n\n return { declaration, references, removable: isRemovable(value, declaration, binding, elsewhere) };\n}\n\n/** Each name one declaration gave this read and this value reads back. */\nfunction aliasesIn(\n source: TSESLint.SourceCode,\n value: TSESTree.Node,\n declarator: TSESTree.VariableDeclarator,\n bindings: readonly Bound[],\n owner: TSESTree.Node | undefined,\n): readonly Alias[] {\n const aliases: Alias[] = [];\n\n for (const binding of bindings) {\n if (enclosingFunction(source, binding.id) !== owner) continue;\n\n const variable = variableOf(source, binding.id);\n const alias = variable === null ? undefined : aliasOf(value, declarator, binding, variable);\n\n if (alias !== undefined) aliases.push(alias);\n }\n\n return aliases;\n}\n\n/** Each name of this file that holds a read of this state and is read back inside the value. */\nfunction aliasesOf(\n source: TSESLint.SourceCode,\n state: TSESTree.Node,\n value: TSESTree.Node,\n owner: TSESTree.Node | undefined,\n written: Written,\n): readonly Alias[] {\n const aliases: Alias[] = [];\n\n for (const declarator of written.declarators) {\n const read = namedRead(declarator);\n\n if (read === undefined || !sameReceiver(source, state, read.receiver)) continue;\n\n aliases.push(...aliasesIn(source, value, declarator, read.bindings, owner));\n }\n\n return aliases;\n}\n\n/** A name the value never spells, so the parameter a rewrite introduces cannot take one the value reads. */\nfunction candidates(source: TSESLint.SourceCode, value: TSESTree.Node): readonly string[] {\n const spelled = new Set(source.getTokens(value).map(token => token.value));\n\n return NAMES.filter(name => !spelled.has(name));\n}\n\n/** A name no enclosing scope holds: only such a name is written without shadowing something the file named. */\nfunction isFree(source: TSESLint.SourceCode, node: TSESTree.Node, name: string): boolean {\n let scope: TSESLint.Scope.Scope | null = source.getScope(node);\n\n while (scope !== null) {\n if (scope.variables.some(variable => variable.name === name)) return false;\n\n scope = scope.upper;\n }\n\n return true;\n}\n\nfunction naming(\n source: TSESLint.SourceCode,\n node: TSESTree.Node,\n value: TSESTree.Node,\n): { free: boolean; name: string | undefined } {\n const usable = candidates(source, value);\n const free = usable.find(name => isFree(source, node, name));\n\n return { free: free !== undefined, name: free ?? usable[0] };\n}\n\n/**\n * The rewrite moves the value into the body of a function, so what the value does outside one does not move with\n * it: an `await` or a `yield` written there no longer parses, an assignment would run at write time instead of\n * here, and a read kept for a later call would freeze on the value of this moment.\n */\nfunction isRewritable(reads: readonly Read[], value: TSESTree.Node, written: Written): boolean {\n if (written.barriers.some(node => within(node, value) && !nested(node, value, written))) return false;\n\n return !reads.some(read => nested(read.node, value, written));\n}\n\n/** The write this rule reports, or nothing: a value that reads another readable replaces no state of its own. */\nfunction siteAt(source: TSESLint.SourceCode, node: TSESTree.CallExpression, written: Written): Site | undefined {\n const write = writeAt(node);\n\n if (write === undefined) return undefined;\n\n const { state, value } = write;\n const direct = snapshotsIn(source, state, value, written).map(call => ({ key: undefined, node: call }));\n const aliases = aliasesOf(source, state, value, enclosingFunction(source, node), written);\n const named = aliases.flatMap(alias => alias.references);\n const reads = [...direct, ...named].sort((left, right) => left.node.range[0] - right.node.range[0]);\n\n if (reads.length === 0) return undefined;\n\n const { free, name } = naming(source, node, value);\n\n return { aliases, free, name, reads, rewritable: isRewritable(reads, value, written), value };\n}\n\n/** An object literal opens with the token that opens a function body, so the updater returns it in parentheses. */\nfunction parenthesized(source: TSESLint.SourceCode, value: TSESTree.Node): boolean {\n return source.getFirstToken(value)?.value === '{' || value.type === AST_NODE_TYPES.SequenceExpression;\n}\n\n/** A shorthand property spells the name once for the key and once for the value; only the value is replaced. */\nfunction replacement(source: TSESLint.SourceCode, read: Read, name: string): string {\n const text = read.key === undefined ? name : `${name}.${read.key}`;\n const { parent } = read.node;\n\n if (parent?.type !== AST_NODE_TYPES.Property || !parent.shorthand || parent.value !== read.node) return text;\n\n return `${source.getText(parent.key)}: ${text}`;\n}\n\n/** The value as its author wrote it, with every read of the state it replaces reading the previous value instead. */\nfunction updater(source: TSESLint.SourceCode, site: Site, name: string): string {\n const start = site.value.range[0];\n const text = source.getText(site.value);\n let rewritten = '';\n let at = 0;\n\n for (const read of site.reads) {\n rewritten += text.slice(at, read.node.range[0] - start) + replacement(source, read, name);\n at = read.node.range[1] - start;\n }\n\n rewritten += text.slice(at);\n\n return `${name} => ${parenthesized(source, site.value) ? `(${rewritten})` : rewritten}`;\n}\n\n/** A comment on the line the declaration ends explains that declaration, and it has nothing to explain without it. */\nfunction trailingComment(\n source: TSESLint.SourceCode,\n declaration: TSESTree.VariableDeclaration,\n next: TSESTree.Token,\n): TSESTree.Token | null {\n const comment = next.type === AST_TOKEN_TYPES.Line || next.type === AST_TOKEN_TYPES.Block;\n\n if (!comment || next.loc.start.line !== declaration.loc.end.line) return next;\n\n return source.getTokenAfter(next, { includeComments: true });\n}\n\n/** The declaration and the space up to the line that follows it, so the statement leaves no blank of its own. */\nfunction removalRange(source: TSESLint.SourceCode, declaration: TSESTree.VariableDeclaration): TSESTree.Range {\n const next = source.getTokenAfter(declaration, { includeComments: true });\n const after = next === null ? null : trailingComment(source, declaration, next);\n\n return after === null ? declaration.range : [declaration.range[0], after.range[0]];\n}\n\n/** One rewrite, whether it is offered as a fix or as a suggestion: the value, and the names it leaves unused. */\nfunction editsOf(source: TSESLint.SourceCode, site: Site, name: string): readonly Edit[] {\n const removals = site.aliases\n .filter(alias => alias.removable)\n .map(alias => ({ range: removalRange(source, alias.declaration), text: '' }));\n\n return [{ range: site.value.range, text: updater(source, site, name) }, ...removals];\n}\n\nexport type { Site, Written };\nexport { editsOf, siteAt };\n"],"names":["NAMES","ARITY","isUpdateCallee","node","AST_NODE_TYPES","stateArgument","state","unwrap","writeAt","first","second","isFunctionLike","snapshotReceiver","callee","sameReceiver","source","candidate","named","variableOf","read","within","outer","nested","value","written","fn","snapshotsIn","reads","call","receiver","patternNames","pattern","names","property","key","propertyName","boundNames","id","namedRead","declarator","bindings","init","isWritten","parent","isReassigned","variable","binding","identifier","separate","declaration","isRemovable","elsewhere","aliasOf","references","aliasesIn","owner","aliases","enclosingFunction","alias","aliasesOf","candidates","spelled","token","name","isFree","scope","naming","usable","free","isRewritable","siteAt","write","direct","left","right","parenthesized","replacement","text","updater","site","start","rewritten","at","trailingComment","next","AST_TOKEN_TYPES","removalRange","after","editsOf","removals"],"mappings":"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"}
1
+ {"version":3,"file":"snapshot-updates.js","sources":["../src/snapshot-updates.ts"],"sourcesContent":["import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport { isFunctionLike, propertyName } from './ast';\nimport { enclosingFunction, unwrap, variableOf } from './model-bindings';\nimport type { Scope, SourceCode, Variable } from './rule';\n\n/** What one file wrote, collected before any write is judged: a rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly barriers: TSESTree.Node[];\n readonly calls: TSESTree.CallExpression[];\n readonly declarators: TSESTree.VariableDeclarator[];\n readonly functions: TSESTree.Node[];\n};\n\n/** One node the rewrite replaces: the parameter goes where the whole read was, a field of it where a field was. */\ntype Read = { readonly key: string | undefined; readonly node: TSESTree.Node };\n\n/** What a declaration gave the read: the value under one name, or fields of it under names of their own. */\ntype Bound = { readonly id: TSESTree.Identifier; readonly key: string | undefined };\n\n/** A name given to the read before the write: the references inside the value, and whether the name has no others. */\ntype Alias = {\n readonly declaration: TSESTree.VariableDeclaration;\n readonly references: readonly Read[];\n readonly removable: boolean;\n};\n\n/** One `update(state, value)` whose value reads the state it replaces, with everything a rewrite of it needs. */\ntype Site = {\n readonly aliases: readonly Alias[];\n readonly free: boolean;\n readonly name: string | undefined;\n readonly reads: readonly Read[];\n readonly rewritable: boolean;\n readonly value: TSESTree.Node;\n};\n\n/** One replacement the rewrite writes, so the rule hands the fixer ranges and never a node of its own. */\ntype Edit = { readonly range: TSESTree.Range; readonly text: string };\n\n/** The parameter the updater is written with, in the order a reader expects to meet it. */\nconst NAMES = ['previous', 'snapshot'];\nconst ARITY = 2;\n\n/** `update(state, value)` and `ctx.update(state, value)` alike: the writer is called by that name (D288). */\nfunction isUpdateCallee(node: TSESTree.Node): boolean {\n if (node.type === AST_NODE_TYPES.Identifier) return node.name === 'update';\n\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n return node.property.type === AST_NODE_TYPES.Identifier && node.property.name === 'update';\n}\n\n/** The readable a write names first, where it is named at all: a call in that position is somebody else's value. */\nfunction stateArgument(node: TSESTree.Node | undefined): TSESTree.Node | undefined {\n if (node === undefined) return undefined;\n\n const state = unwrap(node);\n\n return state.type === AST_NODE_TYPES.Identifier || state.type === AST_NODE_TYPES.MemberExpression ? state : undefined;\n}\n\n/**\n * The shape this rule reads: two arguments, a readable named first, and a value that is not already an updater.\n * A spread in the value position is not the value — the arguments are assembled elsewhere, and what arrives in\n * this position is not written here.\n */\nfunction writeAt(node: TSESTree.CallExpression): { state: TSESTree.Node; value: TSESTree.Node } | undefined {\n if (!isUpdateCallee(node.callee) || node.arguments.length !== ARITY) return undefined;\n\n const [first, second] = node.arguments;\n const state = stateArgument(first);\n\n if (state === undefined || second === undefined || second.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n return isFunctionLike(unwrap(second)) ? undefined : { state, value: second };\n}\n\n/** `s.getSnapshot()`, and the receiver it was read on; a reference that is not called reads nothing. */\nfunction snapshotReceiver(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const { callee } = node;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return undefined;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return undefined;\n\n return node.arguments.length === 0 ? unwrap(callee.object) : undefined;\n}\n\n/**\n * The same readable: a name is the binding it resolves to, and a member expression is the path it spells. Two\n * different objects written the same way are one receiver here, which is what reading shapes rather than types buys.\n */\nfunction sameReceiver(source: SourceCode, state: TSESTree.Node, candidate: TSESTree.Node): boolean {\n if (state.type !== AST_NODE_TYPES.Identifier) {\n return candidate.type === AST_NODE_TYPES.MemberExpression && source.getText(candidate) === source.getText(state);\n }\n\n if (candidate.type !== AST_NODE_TYPES.Identifier) return false;\n\n const named = variableOf(source, state);\n const read = variableOf(source, candidate);\n\n return named === null && read === null ? state.name === candidate.name : named === read;\n}\n\nfunction within(node: TSESTree.Node, outer: TSESTree.Node): boolean {\n return node.range[0] >= outer.range[0] && node.range[1] <= outer.range[1];\n}\n\n/** A function written inside the value runs later than the value does, so what it holds does not travel with it. */\nfunction nested(node: TSESTree.Node, value: TSESTree.Node, written: Written): boolean {\n return written.functions.some(fn => within(fn, value) && within(node, fn));\n}\n\n/** Every read of this state written into the value itself. */\nfunction snapshotsIn(\n source: SourceCode,\n state: TSESTree.Node,\n value: TSESTree.Node,\n written: Written,\n): readonly TSESTree.CallExpression[] {\n const reads: TSESTree.CallExpression[] = [];\n\n for (const call of written.calls) {\n if (!within(call, value)) continue;\n\n const receiver = snapshotReceiver(call);\n\n if (receiver !== undefined && sameReceiver(source, state, receiver)) reads.push(call);\n }\n\n return reads;\n}\n\n/** `const { items } = s.getSnapshot()`: a field taken apart is still that field of the read, under a name. */\nfunction patternNames(pattern: TSESTree.ObjectPattern): readonly Bound[] | undefined {\n const names: Bound[] = [];\n\n for (const property of pattern.properties) {\n if (property.type !== AST_NODE_TYPES.Property || property.value.type !== AST_NODE_TYPES.Identifier)\n return undefined;\n\n const key = propertyName(property);\n\n if (key === undefined) return undefined;\n\n names.push({ id: property.value, key });\n }\n\n return names;\n}\n\n/** Every name a declaration gave the read; a shape this rule cannot write back through gives none. */\nfunction boundNames(id: TSESTree.Node): readonly Bound[] | undefined {\n if (id.type === AST_NODE_TYPES.Identifier) return [{ id, key: undefined }];\n\n return id.type === AST_NODE_TYPES.ObjectPattern ? patternNames(id) : undefined;\n}\n\n/** `const current = s.getSnapshot()`: the names it binds and the receiver it read, where it is written that plainly. */\nfunction namedRead(\n declarator: TSESTree.VariableDeclarator,\n): { bindings: readonly Bound[]; receiver: TSESTree.Node } | undefined {\n const bindings = boundNames(declarator.id);\n\n if (bindings === undefined || declarator.init === null) return undefined;\n\n const init = unwrap(declarator.init);\n const receiver = init.type === AST_NODE_TYPES.CallExpression ? snapshotReceiver(init) : undefined;\n\n return receiver === undefined ? undefined : { bindings, receiver };\n}\n\n/** A name written again does not hold the read it was opened with, so the rewrite has nothing to say about it. */\nfunction isWritten(node: TSESTree.Node): boolean {\n const { parent } = node;\n\n if (parent === undefined) return false;\n\n if (parent.type === AST_NODE_TYPES.AssignmentExpression) return parent.left === node;\n\n return parent.type === AST_NODE_TYPES.UpdateExpression;\n}\n\nfunction isReassigned(variable: Variable, binding: Bound): boolean {\n return variable.references.some(({ identifier }) => identifier !== binding.id && isWritten(identifier));\n}\n\n/** Code where the two overlap is broken before this rule reads it, and a rewrite of it must not overlap either. */\nfunction separate(value: TSESTree.Node, declaration: TSESTree.Node): boolean {\n return !within(value, declaration) && !within(declaration, value);\n}\n\n/** Only a `const` that held the whole read, for this value and nobody else, leaves the file with it. */\nfunction isRemovable(\n value: TSESTree.Node,\n declaration: TSESTree.VariableDeclaration,\n binding: Bound,\n elsewhere: number,\n): boolean {\n return (\n binding.key === undefined &&\n declaration.kind === 'const' &&\n elsewhere === 0 &&\n declaration.declarations.length === 1 &&\n separate(value, declaration)\n );\n}\n\n/** The references this value makes of one name, and whether the declaration behind it goes with them. */\nfunction aliasOf(\n value: TSESTree.Node,\n declarator: TSESTree.VariableDeclarator,\n binding: Bound,\n variable: Variable,\n): Alias | undefined {\n const references: Read[] = [];\n let elsewhere = 0;\n\n for (const { identifier } of variable.references) {\n if (identifier === binding.id) continue;\n\n if (within(identifier, value)) references.push({ key: binding.key, node: identifier });\n else elsewhere += 1;\n }\n\n const declaration = declarator.parent;\n\n if (references.length === 0) return undefined;\n if (declaration.kind !== 'const' && isReassigned(variable, binding)) return undefined;\n\n return { declaration, references, removable: isRemovable(value, declaration, binding, elsewhere) };\n}\n\n/** Each name one declaration gave this read and this value reads back. */\nfunction aliasesIn(\n source: SourceCode,\n value: TSESTree.Node,\n declarator: TSESTree.VariableDeclarator,\n bindings: readonly Bound[],\n owner: TSESTree.Node | undefined,\n): readonly Alias[] {\n const aliases: Alias[] = [];\n\n for (const binding of bindings) {\n if (enclosingFunction(source, binding.id) !== owner) continue;\n\n const variable = variableOf(source, binding.id);\n const alias = variable === null ? undefined : aliasOf(value, declarator, binding, variable);\n\n if (alias !== undefined) aliases.push(alias);\n }\n\n return aliases;\n}\n\n/** Each name of this file that holds a read of this state and is read back inside the value. */\nfunction aliasesOf(\n source: SourceCode,\n state: TSESTree.Node,\n value: TSESTree.Node,\n owner: TSESTree.Node | undefined,\n written: Written,\n): readonly Alias[] {\n const aliases: Alias[] = [];\n\n for (const declarator of written.declarators) {\n const read = namedRead(declarator);\n\n if (read === undefined || !sameReceiver(source, state, read.receiver)) continue;\n\n aliases.push(...aliasesIn(source, value, declarator, read.bindings, owner));\n }\n\n return aliases;\n}\n\n/** A name the value never spells, so the parameter a rewrite introduces cannot take one the value reads. */\nfunction candidates(source: SourceCode, value: TSESTree.Node): readonly string[] {\n const spelled = new Set(source.getTokens(value).map(token => token.value));\n\n return NAMES.filter(name => !spelled.has(name));\n}\n\n/** A name no enclosing scope holds: only such a name is written without shadowing something the file named. */\nfunction isFree(source: SourceCode, node: TSESTree.Node, name: string): boolean {\n let scope: Scope | null = source.getScope(node);\n\n while (scope !== null) {\n if (scope.variables.some(variable => variable.name === name)) return false;\n\n scope = scope.upper;\n }\n\n return true;\n}\n\nfunction naming(\n source: SourceCode,\n node: TSESTree.Node,\n value: TSESTree.Node,\n): { free: boolean; name: string | undefined } {\n const usable = candidates(source, value);\n const free = usable.find(name => isFree(source, node, name));\n\n return { free: free !== undefined, name: free ?? usable[0] };\n}\n\n/**\n * The rewrite moves the value into the body of a function, so what the value does outside one does not move with\n * it: an `await` or a `yield` written there no longer parses, an assignment would run at write time instead of\n * here, and a read kept for a later call would freeze on the value of this moment.\n */\nfunction isRewritable(reads: readonly Read[], value: TSESTree.Node, written: Written): boolean {\n if (written.barriers.some(node => within(node, value) && !nested(node, value, written))) return false;\n\n return !reads.some(read => nested(read.node, value, written));\n}\n\n/** The write this rule reports, or nothing: a value that reads another readable replaces no state of its own. */\nfunction siteAt(source: SourceCode, node: TSESTree.CallExpression, written: Written): Site | undefined {\n const write = writeAt(node);\n\n if (write === undefined) return undefined;\n\n const { state, value } = write;\n const direct = snapshotsIn(source, state, value, written).map(call => ({ key: undefined, node: call }));\n const aliases = aliasesOf(source, state, value, enclosingFunction(source, node), written);\n const named = aliases.flatMap(alias => alias.references);\n const reads = [...direct, ...named].sort((left, right) => left.node.range[0] - right.node.range[0]);\n\n if (reads.length === 0) return undefined;\n\n const { free, name } = naming(source, node, value);\n\n return { aliases, free, name, reads, rewritable: isRewritable(reads, value, written), value };\n}\n\n/** An object literal opens with the token that opens a function body, so the updater returns it in parentheses. */\nfunction parenthesized(source: SourceCode, value: TSESTree.Node): boolean {\n return source.getFirstToken(value)?.value === '{' || value.type === AST_NODE_TYPES.SequenceExpression;\n}\n\n/** A shorthand property spells the name once for the key and once for the value; only the value is replaced. */\nfunction replacement(source: SourceCode, read: Read, name: string): string {\n const text = read.key === undefined ? name : `${name}.${read.key}`;\n const { parent } = read.node;\n\n if (parent?.type !== AST_NODE_TYPES.Property || !parent.shorthand || parent.value !== read.node) return text;\n\n return `${source.getText(parent.key)}: ${text}`;\n}\n\n/** The value as its author wrote it, with every read of the state it replaces reading the previous value instead. */\nfunction updater(source: SourceCode, site: Site, name: string): string {\n const start = site.value.range[0];\n const text = source.getText(site.value);\n let rewritten = '';\n let at = 0;\n\n for (const read of site.reads) {\n rewritten += text.slice(at, read.node.range[0] - start) + replacement(source, read, name);\n at = read.node.range[1] - start;\n }\n\n rewritten += text.slice(at);\n\n return `${name} => ${parenthesized(source, site.value) ? `(${rewritten})` : rewritten}`;\n}\n\n/** A comment on the line the declaration ends explains that declaration, and it has nothing to explain without it. */\nfunction trailingComment(\n source: SourceCode,\n declaration: TSESTree.VariableDeclaration,\n next: TSESTree.Token,\n): TSESTree.Token | null {\n const comment = next.type === AST_TOKEN_TYPES.Line || next.type === AST_TOKEN_TYPES.Block;\n\n if (!comment || next.loc.start.line !== declaration.loc.end.line) return next;\n\n return source.getTokenAfter(next, { includeComments: true });\n}\n\n/** The declaration and the space up to the line that follows it, so the statement leaves no blank of its own. */\nfunction removalRange(source: SourceCode, declaration: TSESTree.VariableDeclaration): TSESTree.Range {\n const next = source.getTokenAfter(declaration, { includeComments: true });\n const after = next === null ? null : trailingComment(source, declaration, next);\n\n return after === null ? declaration.range : [declaration.range[0], after.range[0]];\n}\n\n/** One rewrite, whether it is offered as a fix or as a suggestion: the value, and the names it leaves unused. */\nfunction editsOf(source: SourceCode, site: Site, name: string): readonly Edit[] {\n const removals = site.aliases\n .filter(alias => alias.removable)\n .map(alias => ({ range: removalRange(source, alias.declaration), text: '' }));\n\n return [{ range: site.value.range, text: updater(source, site, name) }, ...removals];\n}\n\nexport type { Site, Written };\nexport { editsOf, siteAt };\n"],"names":["NAMES","ARITY","isUpdateCallee","node","AST_NODE_TYPES","stateArgument","state","unwrap","writeAt","first","second","isFunctionLike","snapshotReceiver","callee","sameReceiver","source","candidate","named","variableOf","read","within","outer","nested","value","written","fn","snapshotsIn","reads","call","receiver","patternNames","pattern","names","property","key","propertyName","boundNames","id","namedRead","declarator","bindings","init","isWritten","parent","isReassigned","variable","binding","identifier","separate","declaration","isRemovable","elsewhere","aliasOf","references","aliasesIn","owner","aliases","enclosingFunction","alias","aliasesOf","candidates","spelled","token","name","isFree","scope","naming","usable","free","isRewritable","siteAt","write","direct","left","right","parenthesized","replacement","text","updater","site","start","rewritten","at","trailingComment","next","AST_TOKEN_TYPES","removalRange","after","editsOf","removals"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
1
+ import type { TSESTree } from '@typescript-eslint/types';
2
+ import type { Scope } from './rule.js';
2
3
  /**
3
4
  * What a rule can learn about an expression without types: its exact string, the fact that it names something
4
5
  * declared elsewhere, or nothing at all because the value is built where it is written.
@@ -11,5 +12,5 @@ type StaticValue = {
11
12
  readonly kind: 'literal';
12
13
  readonly value: string;
13
14
  };
14
- declare function staticValueOf(node: TSESTree.Node, scope: TSESLint.Scope.Scope): StaticValue;
15
+ declare function staticValueOf(node: TSESTree.Node, scope: Scope): StaticValue;
15
16
  export { staticValueOf };
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as r,ASTUtils as p}from"@typescript-eslint/utils";const o={kind:"computed"},a={kind:"external"},d=4,m=new Set([r.ImportDefaultSpecifier,r.ImportNamespaceSpecifier,r.ImportSpecifier]);function y(e){let t=e;for(;t.type===r.TSAsExpression||t.type===r.TSSatisfiesExpression||t.type===r.TSNonNullExpression;)t=t.expression;return t}function S(e){const[t]=e.quasis,n=e.expressions.length>0?null:t?.value.cooked??null;return n===null?o:{kind:"literal",value:n}}function x(e){return e.type===r.Literal?typeof e.value=="string"?{kind:"literal",value:e.value}:o:e.type===r.TemplateLiteral?S(e):void 0}function E(e){const{init:t,parent:n}=e;return t===null||n.kind!=="const"?o:t}function f(e,t){const[n]=p.findVariable(t,e)?.defs??[],i=n?.node;return i===void 0?o:m.has(i.type)?a:i.type===r.VariableDeclarator?E(i):o}function c(e){return"kind"in e}function T(e,t){let n=e;for(;n.type===r.MemberExpression;)n=n.object;if(n.type!==r.Identifier)return o;const i=f(n,t);return!c(i)||i.kind==="external"?a:o}function k(e,t){let n=e;for(let i=0;i<=d;i+=1){const s=y(n),u=x(s);if(u!==void 0)return u;if(s.type===r.MemberExpression)return T(s,t);if(s.type!==r.Identifier)return o;const l=f(s,t);if(c(l))return l;n=l}return o}export{k as staticValueOf};
1
+ import{AST_NODE_TYPES as r}from"@typescript-eslint/types";import{findVariable as p}from"./rule.js";const o={kind:"computed"},f={kind:"external"},d=4,m=new Set([r.ImportDefaultSpecifier,r.ImportNamespaceSpecifier,r.ImportSpecifier]);function y(e){let t=e;for(;t.type===r.TSAsExpression||t.type===r.TSSatisfiesExpression||t.type===r.TSNonNullExpression;)t=t.expression;return t}function x(e){const[t]=e.quasis,n=e.expressions.length>0?null:t?.value.cooked??null;return n===null?o:{kind:"literal",value:n}}function E(e){return e.type===r.Literal?typeof e.value=="string"?{kind:"literal",value:e.value}:o:e.type===r.TemplateLiteral?x(e):void 0}function S(e){const{init:t,parent:n}=e;return t===null||n.kind!=="const"?o:t}function a(e,t){const[n]=p(t,e)?.defs??[],i=n?.node;return i===void 0?o:m.has(i.type)?f:i.type===r.VariableDeclarator?S(i):o}function c(e){return"kind"in e}function T(e,t){let n=e;for(;n.type===r.MemberExpression;)n=n.object;if(n.type!==r.Identifier)return o;const i=a(n,t);return!c(i)||i.kind==="external"?f:o}function k(e,t){let n=e;for(let i=0;i<=d;i+=1){const s=y(n),u=E(s);if(u!==void 0)return u;if(s.type===r.MemberExpression)return T(s,t);if(s.type!==r.Identifier)return o;const l=a(s,t);if(c(l))return l;n=l}return o}export{k as staticValueOf};
2
2
  //# sourceMappingURL=static-value.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"static-value.js","sources":["../src/static-value.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';\n\n/**\n * What a rule can learn about an expression without types: its exact string, the fact that it names something\n * declared elsewhere, or nothing at all because the value is built where it is written.\n */\ntype StaticValue =\n | { readonly kind: 'computed' }\n | { readonly kind: 'external' }\n | { readonly kind: 'literal'; readonly value: string };\n\nconst COMPUTED: StaticValue = { kind: 'computed' };\nconst EXTERNAL: StaticValue = { kind: 'external' };\n\n/** A chain of `const` aliases stays short in real code; the limit only stops a cycle from spinning. */\nconst MAX_DEPTH = 4;\n\nconst IMPORT_BINDINGS = new Set<string>([\n AST_NODE_TYPES.ImportDefaultSpecifier,\n AST_NODE_TYPES.ImportNamespaceSpecifier,\n AST_NODE_TYPES.ImportSpecifier,\n]);\n\nfunction unwrap(node: TSESTree.Node): TSESTree.Node {\n let current = node;\n\n while (\n current.type === AST_NODE_TYPES.TSAsExpression ||\n current.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n current.type === AST_NODE_TYPES.TSNonNullExpression\n ) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction templateOf(node: TSESTree.TemplateLiteral): StaticValue {\n const [quasi] = node.quasis;\n const cooked = node.expressions.length > 0 ? null : (quasi?.value.cooked ?? null);\n\n return cooked === null ? COMPUTED : { kind: 'literal', value: cooked };\n}\n\nfunction literalOf(node: TSESTree.Node): StaticValue | undefined {\n if (node.type === AST_NODE_TYPES.Literal) {\n return typeof node.value === 'string' ? { kind: 'literal', value: node.value } : COMPUTED;\n }\n\n return node.type === AST_NODE_TYPES.TemplateLiteral ? templateOf(node) : undefined;\n}\n\nfunction constantInit(node: TSESTree.VariableDeclarator): StaticValue | TSESTree.Node {\n const { init, parent } = node;\n\n return init === null || parent.kind !== 'const' ? COMPUTED : init;\n}\n\n/** Either the expression a name stands for, or the answer for a name that stands for nothing this rule can read. */\nfunction aliasOf(node: TSESTree.Identifier, scope: TSESLint.Scope.Scope): StaticValue | TSESTree.Node {\n const [definition] = ASTUtils.findVariable(scope, node)?.defs ?? [];\n const declaration = definition?.node;\n\n if (declaration === undefined) return COMPUTED;\n\n if (IMPORT_BINDINGS.has(declaration.type)) return EXTERNAL;\n\n return declaration.type === AST_NODE_TYPES.VariableDeclarator ? constantInit(declaration) : COMPUTED;\n}\n\nfunction isAnswer(resolved: StaticValue | TSESTree.Node): resolved is StaticValue {\n return 'kind' in resolved;\n}\n\n/** `ids.order` reads a value that is written somewhere; `props.id` is built where the declaration is. */\nfunction fromMember(node: TSESTree.MemberExpression, scope: TSESLint.Scope.Scope): StaticValue {\n let root: TSESTree.Node = node;\n\n while (root.type === AST_NODE_TYPES.MemberExpression) root = root.object;\n\n if (root.type !== AST_NODE_TYPES.Identifier) return COMPUTED;\n\n const resolved = aliasOf(root, scope);\n\n return !isAnswer(resolved) || resolved.kind === 'external' ? EXTERNAL : COMPUTED;\n}\n\nfunction staticValueOf(node: TSESTree.Node, scope: TSESLint.Scope.Scope): StaticValue {\n let current = node;\n\n for (let depth = 0; depth <= MAX_DEPTH; depth += 1) {\n const expression = unwrap(current);\n const literal = literalOf(expression);\n\n if (literal !== undefined) return literal;\n\n if (expression.type === AST_NODE_TYPES.MemberExpression) return fromMember(expression, scope);\n\n if (expression.type !== AST_NODE_TYPES.Identifier) return COMPUTED;\n\n const resolved = aliasOf(expression, scope);\n\n if (isAnswer(resolved)) return resolved;\n\n current = resolved;\n }\n\n return COMPUTED;\n}\n\nexport { staticValueOf };\n"],"names":["COMPUTED","EXTERNAL","MAX_DEPTH","IMPORT_BINDINGS","AST_NODE_TYPES","unwrap","node","current","templateOf","quasi","cooked","literalOf","constantInit","init","parent","aliasOf","scope","definition","ASTUtils","declaration","isAnswer","resolved","fromMember","root","staticValueOf","depth","expression","literal"],"mappings":"wEAYA,MAAMA,EAAwB,CAAE,KAAM,UAAU,EAC1CC,EAAwB,CAAE,KAAM,UAAU,EAG1CC,EAAY,EAEZC,EAAkB,IAAI,IAAY,CACtCC,EAAe,uBACfA,EAAe,yBACfA,EAAe,eAChB,CAAA,EAED,SAASC,EAAOC,EAAmB,CACjC,IAAIC,EAAUD,EAEd,KACEC,EAAQ,OAASH,EAAe,gBAChCG,EAAQ,OAASH,EAAe,uBAChCG,EAAQ,OAASH,EAAe,qBAEhCG,EAAUA,EAAQ,WAGpB,OAAOA,CACT,CAEA,SAASC,EAAWF,EAA8B,CAChD,KAAM,CAACG,CAAK,EAAIH,EAAK,OACfI,EAASJ,EAAK,YAAY,OAAS,EAAI,KAAQG,GAAO,MAAM,QAAU,KAE5E,OAAOC,IAAW,KAAOV,EAAW,CAAE,KAAM,UAAW,MAAOU,CAAM,CACtE,CAEA,SAASC,EAAUL,EAAmB,CACpC,OAAIA,EAAK,OAASF,EAAe,QACxB,OAAOE,EAAK,OAAU,SAAW,CAAE,KAAM,UAAW,MAAOA,EAAK,KAAK,EAAKN,EAG5EM,EAAK,OAASF,EAAe,gBAAkBI,EAAWF,CAAI,EAAI,MAC3E,CAEA,SAASM,EAAaN,EAAiC,CACrD,KAAM,CAAE,KAAAO,EAAM,OAAAC,CAAM,EAAKR,EAEzB,OAAOO,IAAS,MAAQC,EAAO,OAAS,QAAUd,EAAWa,CAC/D,CAGA,SAASE,EAAQT,EAA2BU,EAA2B,CACrE,KAAM,CAACC,CAAU,EAAIC,EAAS,aAAaF,EAAOV,CAAI,GAAG,MAAQ,CAAA,EAC3Da,EAAcF,GAAY,KAEhC,OAAIE,IAAgB,OAAkBnB,EAElCG,EAAgB,IAAIgB,EAAY,IAAI,EAAUlB,EAE3CkB,EAAY,OAASf,EAAe,mBAAqBQ,EAAaO,CAAW,EAAInB,CAC9F,CAEA,SAASoB,EAASC,EAAqC,CACrD,MAAO,SAAUA,CACnB,CAGA,SAASC,EAAWhB,EAAiCU,EAA2B,CAC9E,IAAIO,EAAsBjB,EAE1B,KAAOiB,EAAK,OAASnB,EAAe,kBAAkBmB,EAAOA,EAAK,OAElE,GAAIA,EAAK,OAASnB,EAAe,WAAY,OAAOJ,EAEpD,MAAMqB,EAAWN,EAAQQ,EAAMP,CAAK,EAEpC,MAAO,CAACI,EAASC,CAAQ,GAAKA,EAAS,OAAS,WAAapB,EAAWD,CAC1E,CAEA,SAASwB,EAAclB,EAAqBU,EAA2B,CACrE,IAAIT,EAAUD,EAEd,QAASmB,EAAQ,EAAGA,GAASvB,EAAWuB,GAAS,EAAG,CAClD,MAAMC,EAAarB,EAAOE,CAAO,EAC3BoB,EAAUhB,EAAUe,CAAU,EAEpC,GAAIC,IAAY,OAAW,OAAOA,EAElC,GAAID,EAAW,OAAStB,EAAe,iBAAkB,OAAOkB,EAAWI,EAAYV,CAAK,EAE5F,GAAIU,EAAW,OAAStB,EAAe,WAAY,OAAOJ,EAE1D,MAAMqB,EAAWN,EAAQW,EAAYV,CAAK,EAE1C,GAAII,EAASC,CAAQ,EAAG,OAAOA,EAE/Bd,EAAUc,CACZ,CAEA,OAAOrB,CACT"}
1
+ {"version":3,"file":"static-value.js","sources":["../src/static-value.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/types';\nimport type { TSESTree } from '@typescript-eslint/types';\n\nimport type { Scope } from './rule';\nimport { findVariable } from './rule';\n/**\n * What a rule can learn about an expression without types: its exact string, the fact that it names something\n * declared elsewhere, or nothing at all because the value is built where it is written.\n */\ntype StaticValue =\n | { readonly kind: 'computed' }\n | { readonly kind: 'external' }\n | { readonly kind: 'literal'; readonly value: string };\n\nconst COMPUTED: StaticValue = { kind: 'computed' };\nconst EXTERNAL: StaticValue = { kind: 'external' };\n\n/** A chain of `const` aliases stays short in real code; the limit only stops a cycle from spinning. */\nconst MAX_DEPTH = 4;\n\nconst IMPORT_BINDINGS = new Set<string>([\n AST_NODE_TYPES.ImportDefaultSpecifier,\n AST_NODE_TYPES.ImportNamespaceSpecifier,\n AST_NODE_TYPES.ImportSpecifier,\n]);\n\nfunction unwrap(node: TSESTree.Node): TSESTree.Node {\n let current = node;\n\n while (\n current.type === AST_NODE_TYPES.TSAsExpression ||\n current.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n current.type === AST_NODE_TYPES.TSNonNullExpression\n ) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction templateOf(node: TSESTree.TemplateLiteral): StaticValue {\n const [quasi] = node.quasis;\n const cooked = node.expressions.length > 0 ? null : (quasi?.value.cooked ?? null);\n\n return cooked === null ? COMPUTED : { kind: 'literal', value: cooked };\n}\n\nfunction literalOf(node: TSESTree.Node): StaticValue | undefined {\n if (node.type === AST_NODE_TYPES.Literal) {\n return typeof node.value === 'string' ? { kind: 'literal', value: node.value } : COMPUTED;\n }\n\n return node.type === AST_NODE_TYPES.TemplateLiteral ? templateOf(node) : undefined;\n}\n\nfunction constantInit(node: TSESTree.VariableDeclarator): StaticValue | TSESTree.Node {\n const { init, parent } = node;\n\n return init === null || parent.kind !== 'const' ? COMPUTED : init;\n}\n\n/** Either the expression a name stands for, or the answer for a name that stands for nothing this rule can read. */\nfunction aliasOf(node: TSESTree.Identifier, scope: Scope): StaticValue | TSESTree.Node {\n const [definition] = findVariable(scope, node)?.defs ?? [];\n const declaration = definition?.node;\n\n if (declaration === undefined) return COMPUTED;\n\n if (IMPORT_BINDINGS.has(declaration.type)) return EXTERNAL;\n\n return declaration.type === AST_NODE_TYPES.VariableDeclarator ? constantInit(declaration) : COMPUTED;\n}\n\nfunction isAnswer(resolved: StaticValue | TSESTree.Node): resolved is StaticValue {\n return 'kind' in resolved;\n}\n\n/** `ids.order` reads a value that is written somewhere; `props.id` is built where the declaration is. */\nfunction fromMember(node: TSESTree.MemberExpression, scope: Scope): StaticValue {\n let root: TSESTree.Node = node;\n\n while (root.type === AST_NODE_TYPES.MemberExpression) root = root.object;\n\n if (root.type !== AST_NODE_TYPES.Identifier) return COMPUTED;\n\n const resolved = aliasOf(root, scope);\n\n return !isAnswer(resolved) || resolved.kind === 'external' ? EXTERNAL : COMPUTED;\n}\n\nfunction staticValueOf(node: TSESTree.Node, scope: Scope): StaticValue {\n let current = node;\n\n for (let depth = 0; depth <= MAX_DEPTH; depth += 1) {\n const expression = unwrap(current);\n const literal = literalOf(expression);\n\n if (literal !== undefined) return literal;\n\n if (expression.type === AST_NODE_TYPES.MemberExpression) return fromMember(expression, scope);\n\n if (expression.type !== AST_NODE_TYPES.Identifier) return COMPUTED;\n\n const resolved = aliasOf(expression, scope);\n\n if (isAnswer(resolved)) return resolved;\n\n current = resolved;\n }\n\n return COMPUTED;\n}\n\nexport { staticValueOf };\n"],"names":["COMPUTED","EXTERNAL","MAX_DEPTH","IMPORT_BINDINGS","AST_NODE_TYPES","unwrap","node","current","templateOf","quasi","cooked","literalOf","constantInit","init","parent","aliasOf","scope","definition","findVariable","declaration","isAnswer","resolved","fromMember","root","staticValueOf","depth","expression","literal"],"mappings":"mGAcA,MAAMA,EAAwB,CAAE,KAAM,UAAU,EAC1CC,EAAwB,CAAE,KAAM,UAAU,EAG1CC,EAAY,EAEZC,EAAkB,IAAI,IAAY,CACtCC,EAAe,uBACfA,EAAe,yBACfA,EAAe,eAChB,CAAA,EAED,SAASC,EAAOC,EAAmB,CACjC,IAAIC,EAAUD,EAEd,KACEC,EAAQ,OAASH,EAAe,gBAChCG,EAAQ,OAASH,EAAe,uBAChCG,EAAQ,OAASH,EAAe,qBAEhCG,EAAUA,EAAQ,WAGpB,OAAOA,CACT,CAEA,SAASC,EAAWF,EAA8B,CAChD,KAAM,CAACG,CAAK,EAAIH,EAAK,OACfI,EAASJ,EAAK,YAAY,OAAS,EAAI,KAAQG,GAAO,MAAM,QAAU,KAE5E,OAAOC,IAAW,KAAOV,EAAW,CAAE,KAAM,UAAW,MAAOU,CAAM,CACtE,CAEA,SAASC,EAAUL,EAAmB,CACpC,OAAIA,EAAK,OAASF,EAAe,QACxB,OAAOE,EAAK,OAAU,SAAW,CAAE,KAAM,UAAW,MAAOA,EAAK,KAAK,EAAKN,EAG5EM,EAAK,OAASF,EAAe,gBAAkBI,EAAWF,CAAI,EAAI,MAC3E,CAEA,SAASM,EAAaN,EAAiC,CACrD,KAAM,CAAE,KAAAO,EAAM,OAAAC,CAAM,EAAKR,EAEzB,OAAOO,IAAS,MAAQC,EAAO,OAAS,QAAUd,EAAWa,CAC/D,CAGA,SAASE,EAAQT,EAA2BU,EAAY,CACtD,KAAM,CAACC,CAAU,EAAIC,EAAaF,EAAOV,CAAI,GAAG,MAAQ,CAAA,EAClDa,EAAcF,GAAY,KAEhC,OAAIE,IAAgB,OAAkBnB,EAElCG,EAAgB,IAAIgB,EAAY,IAAI,EAAUlB,EAE3CkB,EAAY,OAASf,EAAe,mBAAqBQ,EAAaO,CAAW,EAAInB,CAC9F,CAEA,SAASoB,EAASC,EAAqC,CACrD,MAAO,SAAUA,CACnB,CAGA,SAASC,EAAWhB,EAAiCU,EAAY,CAC/D,IAAIO,EAAsBjB,EAE1B,KAAOiB,EAAK,OAASnB,EAAe,kBAAkBmB,EAAOA,EAAK,OAElE,GAAIA,EAAK,OAASnB,EAAe,WAAY,OAAOJ,EAEpD,MAAMqB,EAAWN,EAAQQ,EAAMP,CAAK,EAEpC,MAAO,CAACI,EAASC,CAAQ,GAAKA,EAAS,OAAS,WAAapB,EAAWD,CAC1E,CAEA,SAASwB,EAAclB,EAAqBU,EAAY,CACtD,IAAIT,EAAUD,EAEd,QAASmB,EAAQ,EAAGA,GAASvB,EAAWuB,GAAS,EAAG,CAClD,MAAMC,EAAarB,EAAOE,CAAO,EAC3BoB,EAAUhB,EAAUe,CAAU,EAEpC,GAAIC,IAAY,OAAW,OAAOA,EAElC,GAAID,EAAW,OAAStB,EAAe,iBAAkB,OAAOkB,EAAWI,EAAYV,CAAK,EAE5F,GAAIU,EAAW,OAAStB,EAAe,WAAY,OAAOJ,EAE1D,MAAMqB,EAAWN,EAAQW,EAAYV,CAAK,EAE1C,GAAII,EAASC,CAAQ,EAAG,OAAOA,EAE/Bd,EAAUc,CACZ,CAEA,OAAOrB,CACT"}
package/oxlintrc.json CHANGED
@@ -4,19 +4,29 @@
4
4
  "opetope/define-feature-property-order": "error",
5
5
  "opetope/id-naming": "error",
6
6
  "opetope/layer-placement": "off",
7
+ "opetope/no-behavior-in-integration": "off",
7
8
  "opetope/no-command-in-deps": "error",
9
+ "opetope/no-dead-abort-check": "warn",
10
+ "opetope/no-event-without-emit": "warn",
11
+ "opetope/no-inline-requires-models": "warn",
8
12
  "opetope/no-internal-imports": "error",
9
13
  "opetope/no-redundant-const-tuple": "error",
10
14
  "opetope/no-retired-vocabulary": "error",
15
+ "opetope/no-single-command-lane": "warn",
11
16
  "opetope/no-snapshot-in-update": "error",
12
17
  "opetope/no-snapshot-read-in-render": "error",
13
18
  "opetope/no-subscribe-outside-models": "off",
14
19
  "opetope/no-write-after-source-write": "error",
20
+ "opetope/prefer-derive": "warn",
15
21
  "opetope/prefer-effect-current": "error",
16
22
  "opetope/prefer-lazy-body-record": "warn",
17
23
  "opetope/prefer-model-selection": "off",
24
+ "opetope/prefer-read": "warn",
25
+ "opetope/prefer-when-over-enabled": "warn",
18
26
  "opetope/require-declared-models": "error",
19
27
  "opetope/require-literal-id": "error",
28
+ "opetope/require-static-feature": "warn",
29
+ "opetope/scope-builder-releases-subscriptions": "error",
20
30
  "opetope/enabled-predicate": "error"
21
31
  }
22
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/lint",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },
@@ -31,17 +31,27 @@
31
31
  "oxlintrc:generate": "node ./scripts/generate-oxlintrc.mjs"
32
32
  },
33
33
  "dependencies": {
34
- "@typescript-eslint/utils": "8.68.0"
34
+ "@typescript-eslint/scope-manager": "8.68.0",
35
+ "@typescript-eslint/types": "8.68.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@jest/globals": "30.5.0",
38
39
  "@typescript-eslint/parser": "8.68.0",
39
- "@typescript-eslint/rule-tester": "8.68.0"
40
+ "@typescript-eslint/rule-tester": "8.68.0",
41
+ "eslint": "9.39.5"
40
42
  },
41
43
  "peerDependencies": {
42
44
  "@typescript-eslint/parser": ">=8.0.0 <9",
43
45
  "eslint": ">=9.0.0 <10"
44
46
  },
47
+ "peerDependenciesMeta": {
48
+ "@typescript-eslint/parser": {
49
+ "optional": true
50
+ },
51
+ "eslint": {
52
+ "optional": true
53
+ }
54
+ },
45
55
  "sideEffects": false,
46
56
  "description": "ESLint rules for authoring Opetope features and models.",
47
57
  "license": "MIT",