@opetope/lint 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +667 -4
- package/README.md +69 -596
- package/dist/ast.d.ts +3 -1
- package/dist/ast.js +1 -1
- package/dist/ast.js.map +1 -1
- package/dist/command-hooks.d.ts +2 -2
- package/dist/command-hooks.js +1 -1
- package/dist/command-hooks.js.map +1 -1
- package/dist/context-members.d.ts +22 -0
- package/dist/context-members.js +2 -0
- package/dist/context-members.js.map +1 -0
- package/dist/declaration-ingress.d.ts +10 -2
- package/dist/declaration-ingress.js +1 -1
- package/dist/declaration-ingress.js.map +1 -1
- package/dist/effect-declarations.d.ts +20 -0
- package/dist/effect-declarations.js +2 -0
- package/dist/effect-declarations.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/retired-vocabulary.d.ts +31 -0
- package/dist/retired-vocabulary.js +2 -0
- package/dist/retired-vocabulary.js.map +1 -0
- package/dist/rules/capture-command-cleanup.js +1 -1
- package/dist/rules/capture-command-cleanup.js.map +1 -1
- package/dist/rules/define-feature-property-order.js +1 -1
- package/dist/rules/define-feature-property-order.js.map +1 -1
- package/dist/rules/enabled-predicate.d.ts +6 -0
- package/dist/rules/enabled-predicate.js +2 -0
- package/dist/rules/enabled-predicate.js.map +1 -0
- package/dist/rules/no-command-in-deps.js +1 -1
- package/dist/rules/no-command-in-deps.js.map +1 -1
- package/dist/rules/no-internal-imports.js +1 -1
- package/dist/rules/no-internal-imports.js.map +1 -1
- package/dist/rules/no-retired-vocabulary.d.ts +5 -0
- package/dist/rules/no-retired-vocabulary.js +2 -0
- package/dist/rules/no-retired-vocabulary.js.map +1 -0
- package/dist/rules/no-write-after-source-write.d.ts +6 -0
- package/dist/rules/no-write-after-source-write.js +2 -0
- package/dist/rules/no-write-after-source-write.js.map +1 -0
- package/dist/rules/prefer-effect-current.js +1 -1
- package/dist/rules/prefer-effect-current.js.map +1 -1
- package/oxlintrc.json +3 -1
- package/package.json +1 -2
- package/README.ru.md +0 -648
- package/dist/rules/when-predicate.d.ts +0 -6
- package/dist/rules/when-predicate.js +0 -2
- package/dist/rules/when-predicate.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AST_NODE_TYPES as
|
|
1
|
+
import{AST_NODE_TYPES as l}from"@typescript-eslint/utils";import{within as u,findProperty as w}from"../ast.js";import{contextMembers as g,fromContext as f}from"../context-members.js";import{methodPath as C,isModelContextFunction as b,commandBody as x}from"../declaration-ingress.js";import{unwrap as d,enclosingFunction as m}from"../model-bindings.js";import{createRule as S}from"../rule.js";const E=["update","signal","rethrowIfCancelled"],T=0;function A(a){const e=a.arguments[1],n=e===void 0?void 0:d(e),r=n?.type===l.ObjectExpression?w(n,"concurrency"):void 0,t=r===void 0?void 0:d(r.value);return t?.type===l.Literal&&t.value==="parallel"}function h(a,e,n,r){return e.type!==l.MemberExpression||e.computed||e.property.type!==l.Identifier||e.property.name!==r?!1:f(a,e.object,n,"signal")}function y(a){if(a.type===l.ThrowStatement||a.type===l.ReturnStatement)return!0;if(a.type!==l.BlockStatement)return!1;const e=a.body.at(-1);return e!==void 0&&y(e)}function I(a,e,n){if(e.type===l.IfStatement)return h(a,d(e.test),n,"aborted")&&y(e.consequent);if(e.type!==l.ExpressionStatement)return!1;const r=d(e.expression);return r.type!==l.CallExpression?!1:f(a,r.callee,n,"rethrowIfCancelled")||h(a,r.callee,n,"throwIfAborted")}function v(a,e,n,r){for(const t of e.body){if(u(n,t))return!1;if(I(a,t,r))return!0}return!1}function O(a,e,n,r){return a.getAncestors(n).some(t=>t.type===l.BlockStatement&&u(t,e.body)&&v(a,t,n,r))}function k(a,e,n){const r=m(a,e);return n.barriers.some(t=>u(t,e.block)&&m(a,t)===r)}function D(a,e,n,r,t,c){if(n===e.block||!k(a,e,c))return;if(n===e.finalizer)return"finally";const{handler:o}=e;if(!(o===null||n!==o))return O(a,o,r,t)?void 0:"catch"}function P(a,e,n,r,t){let c=e;for(const o of a.getAncestors(e).reverse()){if(o===n)return;const i=o.type===l.TryStatement?D(a,o,c,e,r,t):void 0;if(i!==void 0)return i;c=o}}const B=S({create(a){const e=a.sourceCode,n={barriers:[],calls:[],declarators:[]},r=(t,c)=>{const o=g(e,t,T,E,n.declarators),i=c?"parallelCommandCleanup":"captureCommandCleanup";for(const s of n.calls){if(!u(s,t)||!f(e,s.callee,o,"update"))continue;const p=P(e,s,t,o,n);p!==void 0&&a.report({data:{clause:p},messageId:i,node:s})}};return{AwaitExpression(t){n.barriers.push(t)},CallExpression(t){n.calls.push(t)},ForOfStatement(t){t.await&&n.barriers.push(t)},"Program:exit"(){for(const t of n.calls){const c=C(e,t.callee);if(c?.method!=="command"||!b(e,c.fn))continue;const o=x(e,t);o!==void 0&&r(o,A(t))}},VariableDeclarator(t){n.declarators.push(t)}}},meta:{docs:{description:"A command writes the cleanup it owes after an `await` through a commit, not through its writer."},messages:{captureCommandCleanup:"This write is in the `{{clause}}` of a `try` that awaits, and the writer of a command lives only as long as its caller: once the call is cancelled the write is dropped without a record. A cleanup that must run for a cancelled call \u2014 a busy flag, a pending mark \u2014 goes through `const commit = execution.capture(state)` taken before the `await` and `commit.update(\u2026)` here. An answer that must not land after cancellation \u2014 a failure, a result \u2014 belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D319, D330).",parallelCommandCleanup:"This write is in the `{{clause}}` of a `try` that awaits in a `concurrency: 'parallel'` command, and once the call is cancelled it is dropped without a record. A commit does not fix it: calls of this command overlap, so the cleanup of a cancelled call would clear state another call still holds, and a flag shared by parallel calls is racy either way. Keep such state per call \u2014 keyed by its input \u2014 or leave progress to the consumer's `inFlight`. An answer that must not land after cancellation belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D330)."},schema:[],type:"problem"},name:"capture-command-cleanup"});export{B as captureCommandCleanup};
|
|
2
2
|
//# sourceMappingURL=capture-command-cleanup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-command-cleanup.js","sources":["../../src/rules/capture-command-cleanup.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty, propertyName } from '../ast';\nimport { isModelContextFunction, methodPath } from '../declaration-ingress';\nimport type { FunctionNode } from '../model-bindings';\nimport { enclosingFunction, isFunction, localValue, unwrap, variableOf } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'captureCommandCleanup' | 'parallelCommandCleanup';\ntype Clause = 'catch' | 'finally';\ntype Variable = TSESLint.Scope.Variable;\n\n/** What one file wrote, collected before any write is judged: the rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly barriers: TSESTree.Node[];\n readonly calls: TSESTree.CallExpression[];\n readonly declarators: TSESTree.VariableDeclarator[];\n};\n\n/**\n * The names the body of one command reads its context through: the parameter itself, and what was taken out of it —\n * renamed in the parameter list, taken apart by a declaration inside the body, or given a second name by a `const`.\n */\ntype Context = {\n readonly parameter: Variable | undefined;\n readonly rethrow: Set<Variable>;\n readonly signal: Set<Variable>;\n readonly update: Set<Variable>;\n};\n\ntype ContextKey = 'rethrowIfCancelled' | 'signal' | 'update';\n\nconst CONTEXT_KEYS: readonly ContextKey[] = ['update', 'signal', 'rethrowIfCancelled'];\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/** `context.call(run, options?)`: the body a model command is declared with, inline or bound to a local name. */\nfunction commandBody(source: TSESLint.SourceCode, call: TSESTree.CallExpression): FunctionNode | undefined {\n const [body] = call.arguments;\n\n if (body === undefined || body.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n const value = localValue(source, unwrap(body));\n\n return isFunction(value) ? value : undefined;\n}\n\n/**\n * `{ policy: 'parallel' }` written on the declaration itself. A record assembled elsewhere is not read: a policy this\n * rule cannot see is treated as the default queue, and the README says so.\n */\nfunction declaresParallel(call: TSESTree.CallExpression): boolean {\n const options = call.arguments[1];\n const record = options === undefined ? undefined : unwrap(options);\n const policy = record?.type === AST_NODE_TYPES.ObjectExpression ? findProperty(record, 'policy') : undefined;\n const value = policy === undefined ? undefined : unwrap(policy.value);\n\n return value?.type === AST_NODE_TYPES.Literal && value.value === 'parallel';\n}\n\nfunction boundOf(context: Context, key: ContextKey): Set<Variable> {\n if (key === 'update') return context.update;\n\n return key === 'signal' ? context.signal : context.rethrow;\n}\n\nfunction isContextKey(key: string | undefined): key is ContextKey {\n return CONTEXT_KEYS.some(candidate => candidate === key);\n}\n\n/** `({ update, signal: aborting })`: the variables a pattern bound under the keys this rule reads. */\nfunction takeFrom(source: TSESLint.SourceCode, pattern: TSESTree.ObjectPattern, context: Context): void {\n for (const property of pattern.properties) {\n if (property.type !== AST_NODE_TYPES.Property || property.value.type !== AST_NODE_TYPES.Identifier) continue;\n\n const key = propertyName(property);\n const variable = isContextKey(key) ? variableOf(source, property.value) : null;\n\n if (isContextKey(key) && variable !== null) boundOf(context, key).add(variable);\n }\n}\n\nfunction isParameter(source: TSESLint.SourceCode, node: TSESTree.Node, parameter: Variable | undefined): boolean {\n return parameter !== undefined && node.type === AST_NODE_TYPES.Identifier && variableOf(source, node) === parameter;\n}\n\n/** `execution.<key>`, read off the context parameter itself. */\nfunction isContextMember(source: TSESLint.SourceCode, node: TSESTree.Node, context: Context, key: ContextKey): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n\n return (\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === key &&\n isParameter(source, node.object, context.parameter)\n );\n}\n\n/** A name taken from the context under this key, or the key read off the context itself. */\nfunction fromContext(source: TSESLint.SourceCode, node: TSESTree.Node, context: Context, key: ContextKey): boolean {\n if (node.type !== AST_NODE_TYPES.Identifier) return isContextMember(source, node, context, key);\n\n const variable = variableOf(source, node);\n\n return variable !== null && boundOf(context, key).has(variable);\n}\n\n/**\n * `const { update } = execution` takes the context apart one line later, and `const write = update` gives a name the\n * body already holds a second one. Declarations are read in the order they are written, so a name is known before an\n * alias of it. A closure over the writer and a helper handed the context are not followed.\n */\nfunction takeFromDeclarator(\n source: TSESLint.SourceCode,\n declarator: TSESTree.VariableDeclarator,\n context: Context,\n): void {\n const { id, init } = declarator;\n\n if (init === null) return;\n\n const value = unwrap(init);\n\n if (id.type === AST_NODE_TYPES.ObjectPattern) {\n if (isParameter(source, value, context.parameter)) takeFrom(source, id, context);\n\n return;\n }\n\n const variable = id.type === AST_NODE_TYPES.Identifier ? variableOf(source, id) : null;\n const key = CONTEXT_KEYS.find(candidate => fromContext(source, value, context, candidate));\n\n if (variable !== null && key !== undefined) boundOf(context, key).add(variable);\n}\n\n/** The context parameter under the name it arrived with, or nothing when the body took it apart at once. */\nfunction receivedParameter(\n source: TSESLint.SourceCode,\n received: TSESTree.Parameter | undefined,\n): Variable | undefined {\n return received?.type === AST_NODE_TYPES.Identifier ? (variableOf(source, received) ?? undefined) : undefined;\n}\n\n/** The context of a body, whether it arrived whole under a name or taken apart where the body receives it. */\nfunction contextOf(source: TSESLint.SourceCode, body: FunctionNode, written: Written): Context {\n const received = body.params[1];\n const context: Context = {\n parameter: receivedParameter(source, received),\n rethrow: new Set(),\n signal: new Set(),\n update: new Set(),\n };\n\n if (received?.type === AST_NODE_TYPES.ObjectPattern) takeFrom(source, received, context);\n\n for (const declarator of written.declarators) {\n if (within(declarator, body)) takeFromDeclarator(source, declarator, context);\n }\n\n return context;\n}\n\n/** A member read on the signal of this context: `signal.aborted`, `execution.signal.throwIfAborted`. */\nfunction signalMember(source: TSESLint.SourceCode, node: TSESTree.Node, context: Context, member: string): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== member) return false;\n\n return fromContext(source, node.object, context, 'signal');\n}\n\nfunction exits(statement: TSESTree.Statement): boolean {\n if (statement.type === AST_NODE_TYPES.ThrowStatement || statement.type === AST_NODE_TYPES.ReturnStatement)\n return true;\n if (statement.type !== AST_NODE_TYPES.BlockStatement) return false;\n\n const last = statement.body.at(-1);\n\n return last !== undefined && exits(last);\n}\n\n/**\n * A statement past which a `catch` is no longer running for a cancelled caller: `rethrowIfCancelled(cause)`, a\n * `signal.throwIfAborted()`, or an `if (signal.aborted)` that throws or returns. A write after one of them is the\n * author's statement that it must not run for a cancelled call, so it is not the cleanup this rule is about.\n */\nfunction leavesOnCancellation(source: TSESLint.SourceCode, statement: TSESTree.Statement, context: Context): boolean {\n if (statement.type === AST_NODE_TYPES.IfStatement) {\n return signalMember(source, unwrap(statement.test), context, 'aborted') && exits(statement.consequent);\n }\n\n if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;\n\n const expression = unwrap(statement.expression);\n\n if (expression.type !== AST_NODE_TYPES.CallExpression) return false;\n\n return (\n fromContext(source, expression.callee, context, 'rethrowIfCancelled') ||\n signalMember(source, expression.callee, context, 'throwIfAborted')\n );\n}\n\n/** Whether a statement written before the write in this block already left for a cancelled caller. */\nfunction guardedBlock(\n source: TSESLint.SourceCode,\n block: TSESTree.BlockStatement,\n write: TSESTree.Node,\n context: Context,\n): boolean {\n for (const statement of block.body) {\n if (within(write, statement)) return false;\n if (leavesOnCancellation(source, statement, context)) return true;\n }\n\n return false;\n}\n\n/**\n * A guard counts where it precedes the write in the block that holds it or in any block around that one, up to the\n * `catch` itself: `if (cause instanceof Refused) { rethrowIfCancelled(cause); update(failed, true) }` is guarded, and\n * a guard inside a block the write is not in guards nothing.\n */\nfunction guardedCatch(\n source: TSESLint.SourceCode,\n handler: TSESTree.CatchClause,\n write: TSESTree.Node,\n context: Context,\n): boolean {\n return source\n .getAncestors(write)\n .some(\n ancestor =>\n ancestor.type === AST_NODE_TYPES.BlockStatement &&\n within(ancestor, handler.body) &&\n guardedBlock(source, ancestor, write, context),\n );\n}\n\n/** Whether the protected block suspends the function the `try` belongs to; an `await` in a nested callback does not. */\nfunction awaits(source: TSESLint.SourceCode, statement: TSESTree.TryStatement, written: Written): boolean {\n const owner = enclosingFunction(source, statement);\n\n return written.barriers.some(\n barrier => within(barrier, statement.block) && enclosingFunction(source, barrier) === owner,\n );\n}\n\n/** Where this `try` holds the write it was reached from: a cleanup clause of an awaited block, or nowhere. */\nfunction clauseOf(\n source: TSESLint.SourceCode,\n statement: TSESTree.TryStatement,\n child: TSESTree.Node,\n write: TSESTree.CallExpression,\n context: Context,\n written: Written,\n): Clause | undefined {\n if (child === statement.block || !awaits(source, statement, written)) return undefined;\n if (child === statement.finalizer) return 'finally';\n\n const { handler } = statement;\n\n if (handler === null || child !== handler) return undefined;\n\n return guardedCatch(source, handler, write, context) ? undefined : 'catch';\n}\n\n/** The cleanup clause of the innermost awaited `try` that holds this write, walking out no further than the body. */\nfunction cleanupClause(\n source: TSESLint.SourceCode,\n write: TSESTree.CallExpression,\n body: FunctionNode,\n context: Context,\n written: Written,\n): Clause | undefined {\n let child: TSESTree.Node = write;\n\n for (const parent of source.getAncestors(write).reverse()) {\n if (parent === body) return undefined;\n\n const clause =\n parent.type === AST_NODE_TYPES.TryStatement\n ? clauseOf(source, parent, child, write, context, written)\n : undefined;\n\n if (clause !== undefined) return clause;\n\n child = parent;\n }\n\n return undefined;\n}\n\nconst captureCommandCleanup = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const written: Written = { barriers: [], calls: [], declarators: [] };\n\n const reportWrites = (body: FunctionNode, parallel: boolean): void => {\n const received = contextOf(source, body, written);\n const messageId: MessageIds = parallel ? 'parallelCommandCleanup' : 'captureCommandCleanup';\n\n for (const call of written.calls) {\n if (!within(call, body) || !fromContext(source, call.callee, received, 'update')) continue;\n\n const clause = cleanupClause(source, call, body, received, written);\n\n if (clause !== undefined) context.report({ data: { clause }, messageId, node: call });\n }\n };\n\n return {\n AwaitExpression(node): void {\n written.barriers.push(node);\n },\n CallExpression(node): void {\n written.calls.push(node);\n },\n ForOfStatement(node): void {\n if (node.await) written.barriers.push(node);\n },\n 'Program:exit'(): void {\n for (const call of written.calls) {\n const named = methodPath(source, call.callee);\n\n if (named?.method !== 'call' || !isModelContextFunction(source, named.fn)) continue;\n\n const body = commandBody(source, call);\n\n if (body !== undefined) reportWrites(body, declaresParallel(call));\n }\n },\n VariableDeclarator(node): void {\n written.declarators.push(node);\n },\n };\n },\n meta: {\n docs: {\n description: 'A command writes the cleanup it owes after an `await` through a commit, not through its writer.',\n },\n messages: {\n captureCommandCleanup:\n 'This write is in the `{{clause}}` of a `try` that awaits, and the writer of a command lives only as long as its caller: once the call is cancelled the write is dropped without a record. A cleanup that must run for a cancelled call — a busy flag, a pending mark — goes through `const commit = execution.capture(state)` taken before the `await` and `commit.update(…)` here. An answer that must not land after cancellation — a failure, a result — belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D319, D330).',\n parallelCommandCleanup:\n \"This write is in the `{{clause}}` of a `try` that awaits in a `policy: 'parallel'` command, and once the call is cancelled it is dropped without a record. A commit does not fix it: calls of this command overlap, so the cleanup of a cancelled call would clear state another call still holds, and a flag shared by parallel calls is racy either way. Keep such state per call — keyed by its input — or leave progress to the consumer's `inFlight`. An answer that must not land after cancellation belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D330).\",\n },\n schema: [],\n type: 'problem',\n },\n name: 'capture-command-cleanup',\n});\n\nexport { captureCommandCleanup };\n"],"names":["CONTEXT_KEYS","within","node","outer","commandBody","source","call","body","AST_NODE_TYPES","value","localValue","unwrap","isFunction","declaresParallel","options","record","policy","findProperty","boundOf","context","key","isContextKey","candidate","takeFrom","pattern","property","propertyName","variable","variableOf","isParameter","parameter","isContextMember","fromContext","takeFromDeclarator","declarator","id","init","receivedParameter","received","contextOf","written","signalMember","member","exits","statement","last","leavesOnCancellation","expression","guardedBlock","block","write","guardedCatch","handler","ancestor","awaits","owner","enclosingFunction","barrier","clauseOf","child","cleanupClause","parent","clause","captureCommandCleanup","createRule","reportWrites","parallel","messageId","named","methodPath","isModelContextFunction"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"capture-command-cleanup.js","sources":["../../src/rules/capture-command-cleanup.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty, within } from '../ast';\nimport type { ContextMembers } from '../context-members';\nimport { contextMembers, fromContext } from '../context-members';\nimport { commandBody, isModelContextFunction, methodPath } from '../declaration-ingress';\nimport type { FunctionNode } from '../model-bindings';\nimport { enclosingFunction, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'captureCommandCleanup' | 'parallelCommandCleanup';\ntype Clause = 'catch' | 'finally';\n\n/** What one file wrote, collected before any write is judged: the rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly barriers: TSESTree.Node[];\n readonly calls: TSESTree.CallExpression[];\n readonly declarators: TSESTree.VariableDeclarator[];\n};\n\ntype ContextKey = 'rethrowIfCancelled' | 'signal' | 'update';\ntype Context = ContextMembers<ContextKey>;\n\n/** D386: the three names this rule reads off the context of a command body, which is its one parameter. */\nconst CONTEXT_KEYS: readonly ContextKey[] = ['update', 'signal', 'rethrowIfCancelled'];\nconst CONTEXT_POSITION = 0;\n\n/**\n * D387: `{ concurrency: 'parallel' }` written on the declaration itself. A record assembled elsewhere is not read: a\n * discipline this rule cannot see is treated as the default queue, and the README says so.\n */\nfunction declaresParallel(command: TSESTree.CallExpression): boolean {\n const options = command.arguments[1];\n const record = options === undefined ? undefined : unwrap(options);\n const concurrency =\n record?.type === AST_NODE_TYPES.ObjectExpression ? findProperty(record, 'concurrency') : undefined;\n const value = concurrency === undefined ? undefined : unwrap(concurrency.value);\n\n return value?.type === AST_NODE_TYPES.Literal && value.value === 'parallel';\n}\n\n/** A member read on the signal of this context: `signal.aborted`, `execution.signal.throwIfAborted`. */\nfunction signalMember(source: TSESLint.SourceCode, node: TSESTree.Node, context: Context, member: string): boolean {\n if (node.type !== AST_NODE_TYPES.MemberExpression || node.computed) return false;\n if (node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== member) return false;\n\n return fromContext(source, node.object, context, 'signal');\n}\n\nfunction exits(statement: TSESTree.Statement): boolean {\n if (statement.type === AST_NODE_TYPES.ThrowStatement || statement.type === AST_NODE_TYPES.ReturnStatement)\n return true;\n if (statement.type !== AST_NODE_TYPES.BlockStatement) return false;\n\n const last = statement.body.at(-1);\n\n return last !== undefined && exits(last);\n}\n\n/**\n * A statement past which a `catch` is no longer running for a cancelled caller: `rethrowIfCancelled(cause)`, a\n * `signal.throwIfAborted()`, or an `if (signal.aborted)` that throws or returns. A write after one of them is the\n * author's statement that it must not run for a cancelled call, so it is not the cleanup this rule is about.\n */\nfunction leavesOnCancellation(source: TSESLint.SourceCode, statement: TSESTree.Statement, context: Context): boolean {\n if (statement.type === AST_NODE_TYPES.IfStatement) {\n return signalMember(source, unwrap(statement.test), context, 'aborted') && exits(statement.consequent);\n }\n\n if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;\n\n const expression = unwrap(statement.expression);\n\n if (expression.type !== AST_NODE_TYPES.CallExpression) return false;\n\n return (\n fromContext(source, expression.callee, context, 'rethrowIfCancelled') ||\n signalMember(source, expression.callee, context, 'throwIfAborted')\n );\n}\n\n/** Whether a statement written before the write in this block already left for a cancelled caller. */\nfunction guardedBlock(\n source: TSESLint.SourceCode,\n block: TSESTree.BlockStatement,\n write: TSESTree.Node,\n context: Context,\n): boolean {\n for (const statement of block.body) {\n if (within(write, statement)) return false;\n if (leavesOnCancellation(source, statement, context)) return true;\n }\n\n return false;\n}\n\n/**\n * A guard counts where it precedes the write in the block that holds it or in any block around that one, up to the\n * `catch` itself: `if (cause instanceof Refused) { rethrowIfCancelled(cause); update(failed, true) }` is guarded, and\n * a guard inside a block the write is not in guards nothing.\n */\nfunction guardedCatch(\n source: TSESLint.SourceCode,\n handler: TSESTree.CatchClause,\n write: TSESTree.Node,\n context: Context,\n): boolean {\n return source\n .getAncestors(write)\n .some(\n ancestor =>\n ancestor.type === AST_NODE_TYPES.BlockStatement &&\n within(ancestor, handler.body) &&\n guardedBlock(source, ancestor, write, context),\n );\n}\n\n/** Whether the protected block suspends the function the `try` belongs to; an `await` in a nested callback does not. */\nfunction awaits(source: TSESLint.SourceCode, statement: TSESTree.TryStatement, written: Written): boolean {\n const owner = enclosingFunction(source, statement);\n\n return written.barriers.some(\n barrier => within(barrier, statement.block) && enclosingFunction(source, barrier) === owner,\n );\n}\n\n/** Where this `try` holds the write it was reached from: a cleanup clause of an awaited block, or nowhere. */\nfunction clauseOf(\n source: TSESLint.SourceCode,\n statement: TSESTree.TryStatement,\n child: TSESTree.Node,\n write: TSESTree.CallExpression,\n context: Context,\n written: Written,\n): Clause | undefined {\n if (child === statement.block || !awaits(source, statement, written)) return undefined;\n if (child === statement.finalizer) return 'finally';\n\n const { handler } = statement;\n\n if (handler === null || child !== handler) return undefined;\n\n return guardedCatch(source, handler, write, context) ? undefined : 'catch';\n}\n\n/** The cleanup clause of the innermost awaited `try` that holds this write, walking out no further than the body. */\nfunction cleanupClause(\n source: TSESLint.SourceCode,\n write: TSESTree.CallExpression,\n body: FunctionNode,\n context: Context,\n written: Written,\n): Clause | undefined {\n let child: TSESTree.Node = write;\n\n for (const parent of source.getAncestors(write).reverse()) {\n if (parent === body) return undefined;\n\n const clause =\n parent.type === AST_NODE_TYPES.TryStatement\n ? clauseOf(source, parent, child, write, context, written)\n : undefined;\n\n if (clause !== undefined) return clause;\n\n child = parent;\n }\n\n return undefined;\n}\n\nconst captureCommandCleanup = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const written: Written = { barriers: [], calls: [], declarators: [] };\n\n const reportWrites = (body: FunctionNode, parallel: boolean): void => {\n const received = contextMembers(source, body, CONTEXT_POSITION, CONTEXT_KEYS, written.declarators);\n const messageId: MessageIds = parallel ? 'parallelCommandCleanup' : 'captureCommandCleanup';\n\n for (const call of written.calls) {\n if (!within(call, body) || !fromContext(source, call.callee, received, 'update')) continue;\n\n const clause = cleanupClause(source, call, body, received, written);\n\n if (clause !== undefined) context.report({ data: { clause }, messageId, node: call });\n }\n };\n\n return {\n AwaitExpression(node): void {\n written.barriers.push(node);\n },\n CallExpression(node): void {\n written.calls.push(node);\n },\n ForOfStatement(node): void {\n if (node.await) written.barriers.push(node);\n },\n 'Program:exit'(): void {\n for (const call of written.calls) {\n const named = methodPath(source, call.callee);\n\n if (named?.method !== 'command' || !isModelContextFunction(source, named.fn)) continue;\n\n const body = commandBody(source, call);\n\n if (body !== undefined) reportWrites(body, declaresParallel(call));\n }\n },\n VariableDeclarator(node): void {\n written.declarators.push(node);\n },\n };\n },\n meta: {\n docs: {\n description: 'A command writes the cleanup it owes after an `await` through a commit, not through its writer.',\n },\n messages: {\n captureCommandCleanup:\n 'This write is in the `{{clause}}` of a `try` that awaits, and the writer of a command lives only as long as its caller: once the call is cancelled the write is dropped without a record. A cleanup that must run for a cancelled call — a busy flag, a pending mark — goes through `const commit = execution.capture(state)` taken before the `await` and `commit.update(…)` here. An answer that must not land after cancellation — a failure, a result — belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D319, D330).',\n parallelCommandCleanup:\n \"This write is in the `{{clause}}` of a `try` that awaits in a `concurrency: 'parallel'` command, and once the call is cancelled it is dropped without a record. A commit does not fix it: calls of this command overlap, so the cleanup of a cancelled call would clear state another call still holds, and a flag shared by parallel calls is racy either way. Keep such state per call — keyed by its input — or leave progress to the consumer's `inFlight`. An answer that must not land after cancellation belongs in a `catch` after `rethrowIfCancelled(cause)` (D288, D330).\",\n },\n schema: [],\n type: 'problem',\n },\n name: 'capture-command-cleanup',\n});\n\nexport { captureCommandCleanup };\n"],"names":["CONTEXT_KEYS","CONTEXT_POSITION","declaresParallel","command","options","record","unwrap","concurrency","AST_NODE_TYPES","findProperty","value","signalMember","source","node","context","member","fromContext","exits","statement","last","leavesOnCancellation","expression","guardedBlock","block","write","within","guardedCatch","handler","ancestor","awaits","written","owner","enclosingFunction","barrier","clauseOf","child","cleanupClause","body","parent","clause","captureCommandCleanup","createRule","reportWrites","parallel","received","contextMembers","messageId","call","named","methodPath","isModelContextFunction","commandBody"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AST_NODE_TYPES as
|
|
1
|
+
import{AST_NODE_TYPES as p}from"@typescript-eslint/utils";import{calleeName as u,propertyName as c}from"../ast.js";import{createRule as m}from"../rule.js";const d=["imports","requires","own","exports","provides","when","body"],l=new Map([["defineFeature",d],["defineFeature.body",["own","exports","provides"]]]);function a(o){const n=c(o);return n===void 0?-1:d.indexOf(n)}function x(o,n){const e=[];for(const r of o){const t=c(r);if(t===void 0||!n.includes(t))return;e.push(d.indexOf(t))}return e}function g(o){let n=-1;for(const[e,r]of o.entries()){if(r<n)return e;n=r}return-1}function y(o,n){return o.getCommentsInside(n).some(e=>!n.properties.some(r=>r.range[0]<=e.range[0]&&e.range[1]<=r.range[1]))}function*O(o,n,e){const r=[...e].sort((t,s)=>a(t)-a(s));for(const[t,s]of e.entries()){const i=r[t];i!==void 0&&i!==s&&(yield o.replaceText(s,n.getText(i)))}}const h=m({create(o){const n=(e,r)=>{const t=x(e.properties,r);if(t===void 0)return;const s=g(t),i=e.properties[s];i!==void 0&&o.report({data:{order:r.join(", ")},fix:y(o.sourceCode,e)?null:f=>[...O(f,o.sourceCode,e.properties)],messageId:"sectionOrder",node:i})};return{CallExpression(e){const[,r]=e.arguments,t=l.get(u(e)??"");t===void 0||r?.type!==p.ObjectExpression||n(r,t)}}},meta:{docs:{description:"The sections of `defineFeature` keep the declaration order of the specification."},fixable:"code",messages:{sectionOrder:"A feature declares its sections in one order: {{order}} (spec \xA72.1, D186, D280)."},schema:[],type:"layout"},name:"define-feature-property-order"});export{h as defineFeaturePropertyOrder};
|
|
2
2
|
//# sourceMappingURL=define-feature-property-order.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-feature-property-order.js","sources":["../../src/rules/define-feature-property-order.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'sectionOrder';\ntype Properties = readonly TSESTree.ObjectLiteralElement[];\n\n/**\n * The declaration order of the sections: the edges, the stages, then the lifetime and the loader that replaces the\n * last three stages. The identity is no longer among them — a feature names itself with its first argument (D280).\n * The specification names the sections; the order is the one a key sorter can also be configured to hold, so a host\n * runs one convention instead of two (D186).\n */\nconst SECTIONS = ['imports', 'requires', 'own', 'exports', 'provides', 'when', 'body'];\n\nfunction sectionIndex(property: TSESTree.ObjectLiteralElement): number {\n const name = propertyName(property);\n\n return name === undefined ? -1 : SECTIONS.indexOf(name);\n}\n\n/** The declared sections in source order, or `undefined` when a key is not a section this
|
|
1
|
+
{"version":3,"file":"define-feature-property-order.js","sources":["../../src/rules/define-feature-property-order.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'sectionOrder';\ntype Properties = readonly TSESTree.ObjectLiteralElement[];\n\n/**\n * The declaration order of the sections: the edges, the stages, then the lifetime and the loader that replaces the\n * last three stages. The identity is no longer among them — a feature names itself with its first argument (D280).\n * The specification names the sections; the order is the one a key sorter can also be configured to hold, so a host\n * runs one convention instead of two (D186).\n *\n * D434: sections are written at two callees, not one. A body file writes three of them, and its call was read as\n * some other function's, so the one order held at `defineFeature` and nowhere else. Both are read against the same\n * order — the body's three are a subsequence of it — and each against its own list, because a refusal that\n * enumerates seven where three are taken names four keys the author cannot write there (D207).\n */\nconst SECTIONS = ['imports', 'requires', 'own', 'exports', 'provides', 'when', 'body'];\nconst CALLEE_SECTIONS = new Map([\n ['defineFeature', SECTIONS],\n ['defineFeature.body', ['own', 'exports', 'provides']],\n]);\n\nfunction sectionIndex(property: TSESTree.ObjectLiteralElement): number {\n const name = propertyName(property);\n\n return name === undefined ? -1 : SECTIONS.indexOf(name);\n}\n\n/** The declared sections in source order, or `undefined` when a key is not a section this callee places. */\nfunction sectionOrder(properties: Properties, sections: readonly string[]): readonly number[] | undefined {\n const order: number[] = [];\n\n for (const property of properties) {\n const name = propertyName(property);\n\n if (name === undefined || !sections.includes(name)) return undefined;\n\n order.push(SECTIONS.indexOf(name));\n }\n\n return order;\n}\n\nfunction firstMisplaced(order: readonly number[]): number {\n let previous = -1;\n\n for (const [index, value] of order.entries()) {\n if (value < previous) return index;\n\n previous = value;\n }\n\n return -1;\n}\n\n/**\n * A comment between two sections belongs to neither of them, so reordering would move it away from the line it\n * explains. Such an object is reported without a fix; a comment inside a section travels with it.\n */\nfunction hasSeparatingComment(sourceCode: TSESLint.SourceCode, object: TSESTree.ObjectExpression): boolean {\n return sourceCode\n .getCommentsInside(object)\n .some(\n comment =>\n !object.properties.some(\n property => property.range[0] <= comment.range[0] && comment.range[1] <= property.range[1],\n ),\n );\n}\n\nfunction* reorder(\n fixer: TSESLint.RuleFixer,\n sourceCode: TSESLint.SourceCode,\n properties: Properties,\n): Generator<TSESLint.RuleFix> {\n const sorted = [...properties].sort((left, right) => sectionIndex(left) - sectionIndex(right));\n\n for (const [index, property] of properties.entries()) {\n const replacement = sorted[index];\n\n if (replacement !== undefined && replacement !== property) {\n yield fixer.replaceText(property, sourceCode.getText(replacement));\n }\n }\n}\n\nconst defineFeaturePropertyOrder = createRule<[], MessageIds>({\n create(context) {\n const check = (object: TSESTree.ObjectExpression, sections: readonly string[]): void => {\n const order = sectionOrder(object.properties, sections);\n\n if (order === undefined) return;\n\n const misplaced = firstMisplaced(order);\n const property = object.properties[misplaced];\n\n if (property === undefined) return;\n\n context.report({\n data: { order: sections.join(', ') },\n fix: hasSeparatingComment(context.sourceCode, object)\n ? null\n : fixer => [...reorder(fixer, context.sourceCode, object.properties)],\n messageId: 'sectionOrder',\n node: property,\n });\n };\n\n return {\n CallExpression(node): void {\n // D280, D207: the identity is the first argument of both callees, so the sections are the second.\n const [, sections] = node.arguments;\n const declared = CALLEE_SECTIONS.get(calleeName(node) ?? '');\n\n if (declared === undefined || sections?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n check(sections, declared);\n },\n };\n },\n meta: {\n docs: {\n description: 'The sections of `defineFeature` keep the declaration order of the specification.',\n },\n fixable: 'code',\n messages: {\n sectionOrder: 'A feature declares its sections in one order: {{order}} (spec §2.1, D186, D280).',\n },\n schema: [],\n type: 'layout',\n },\n name: 'define-feature-property-order',\n});\n\nexport { defineFeaturePropertyOrder };\n"],"names":["SECTIONS","CALLEE_SECTIONS","sectionIndex","property","name","propertyName","sectionOrder","properties","sections","order","firstMisplaced","previous","index","value","hasSeparatingComment","sourceCode","object","comment","reorder","fixer","sorted","left","right","replacement","defineFeaturePropertyOrder","createRule","context","check","misplaced","node","declared","calleeName","AST_NODE_TYPES"],"mappings":"2JAoBA,MAAMA,EAAW,CAAC,UAAW,WAAY,MAAO,UAAW,WAAY,OAAQ,MAAM,EAC/EC,EAAkB,IAAI,IAAI,CAC9B,CAAC,gBAAiBD,CAAQ,EAC1B,CAAC,qBAAsB,CAAC,MAAO,UAAW,UAAU,CAAC,CACtD,CAAA,EAED,SAASE,EAAaC,EAAuC,CAC3D,MAAMC,EAAOC,EAAaF,CAAQ,EAElC,OAAOC,IAAS,OAAY,GAAKJ,EAAS,QAAQI,CAAI,CACxD,CAGA,SAASE,EAAaC,EAAwBC,EAA2B,CACvE,MAAMC,EAAkB,CAAA,EAExB,UAAWN,KAAYI,EAAY,CACjC,MAAMH,EAAOC,EAAaF,CAAQ,EAElC,GAAIC,IAAS,QAAa,CAACI,EAAS,SAASJ,CAAI,EAAG,OAEpDK,EAAM,KAAKT,EAAS,QAAQI,CAAI,CAAC,CACnC,CAEA,OAAOK,CACT,CAEA,SAASC,EAAeD,EAAwB,CAC9C,IAAIE,EAAW,GAEf,SAAW,CAACC,EAAOC,CAAK,IAAKJ,EAAM,QAAO,EAAI,CAC5C,GAAII,EAAQF,EAAU,OAAOC,EAE7BD,EAAWE,CACb,CAEA,MAAO,EACT,CAMA,SAASC,EAAqBC,EAAiCC,EAAiC,CAC9F,OAAOD,EACJ,kBAAkBC,CAAM,EACxB,KACCC,GACE,CAACD,EAAO,WAAW,KACjBb,GAAYA,EAAS,MAAM,CAAC,GAAKc,EAAQ,MAAM,CAAC,GAAKA,EAAQ,MAAM,CAAC,GAAKd,EAAS,MAAM,CAAC,CAAC,CAC3F,CAET,CAEA,SAAUe,EACRC,EACAJ,EACAR,EAAsB,CAEtB,MAAMa,EAAS,CAAC,GAAGb,CAAU,EAAE,KAAK,CAACc,EAAMC,IAAUpB,EAAamB,CAAI,EAAInB,EAAaoB,CAAK,CAAC,EAE7F,SAAW,CAACV,EAAOT,CAAQ,IAAKI,EAAW,QAAO,EAAI,CACpD,MAAMgB,EAAcH,EAAOR,CAAK,EAE5BW,IAAgB,QAAaA,IAAgBpB,IAC/C,MAAMgB,EAAM,YAAYhB,EAAUY,EAAW,QAAQQ,CAAW,CAAC,EAErE,CACF,CAEA,MAAMC,EAA6BC,EAA2B,CAC5D,OAAOC,EAAO,CACZ,MAAMC,EAAQ,CAACX,EAAmCR,IAAqC,CACrF,MAAMC,EAAQH,EAAaU,EAAO,WAAYR,CAAQ,EAEtD,GAAIC,IAAU,OAAW,OAEzB,MAAMmB,EAAYlB,EAAeD,CAAK,EAChCN,EAAWa,EAAO,WAAWY,CAAS,EAExCzB,IAAa,QAEjBuB,EAAQ,OAAO,CACb,KAAM,CAAE,MAAOlB,EAAS,KAAK,IAAI,CAAC,EAClC,IAAKM,EAAqBY,EAAQ,WAAYV,CAAM,EAChD,KACAG,GAAS,CAAC,GAAGD,EAAQC,EAAOO,EAAQ,WAAYV,EAAO,UAAU,CAAC,EACtE,UAAW,eACX,KAAMb,CACP,CAAA,CACH,EAEA,MAAO,CACL,eAAe0B,EAAI,CAEjB,KAAM,CAAA,CAAGrB,CAAQ,EAAIqB,EAAK,UACpBC,EAAW7B,EAAgB,IAAI8B,EAAWF,CAAI,GAAK,EAAE,EAEvDC,IAAa,QAAatB,GAAU,OAASwB,EAAe,kBAEhEL,EAAMnB,EAAUsB,CAAQ,CAC1B,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,kFACd,EACD,QAAS,OACT,SAAU,CACR,aAAc,qFACf,EACD,OAAQ,CAAA,EACR,KAAM,QACP,EACD,KAAM,+BACP,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type MessageIds = 'enabledIsAsync' | 'enabledReturnsBareSource' | 'enabledReturnsSource';
|
|
3
|
+
declare const enabledPredicate: TSESLint.RuleModule<MessageIds, [], unknown, TSESLint.RuleListener> & {
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
export { enabledPredicate };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AST_NODE_TYPES as a}from"@typescript-eslint/utils";import{propertyName as m,isFunctionLike as h,calleeName as y,patternKeys as d,memberRoot as l,memberFirstStep as S}from"../ast.js";import{createRule as w}from"../rule.js";const I=new Set(["pipe","register","slot"]),x=new Set(["exports","imports","own","read"]),b=new Set(["exports","imports","own"]);function E(n){const{parent:e}=n;if(e.type!==a.CallExpression||e.arguments.length<2)return!1;const t=y(e);return t!==void 0&&I.has(t)&&e.arguments.at(-1)===n}function c(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==a.ObjectPattern))return[...d(e)].some(t=>x.has(t))?e:void 0}function T(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==a.Identifier))return e}function g(n,e){const t=l(e),r=S(e);return t.type!==a.Identifier||t.name!==n.name?!1:r!==void 0&&b.has(r)}function A(n,e){const t=l(e);return t.type!==a.Identifier?!1:b.has(t.name)&&d(n).has(t.name)}function R(n,e){const t=c(n);if(t!==void 0)return A(t,e)?"read":void 0;const r=T(n);if(r!==void 0)return g(r,e)?`${r.name}.read`:void 0}function O(n,e){const t=e.properties.find(s=>{const i=m(s);return i===void 0||i>"read"});if(t!==void 0)return n.insertTextBefore(t,"read, ");const r=e.properties.at(-1);return r===void 0?void 0:n.insertTextAfter(r,", read")}function C(n,e){return n.params.some(t=>t.type===a.Identifier&&t.name===e.name)}function N(n,e){const{parent:t}=n;return t.type===a.ObjectExpression&&E(t)||c(e)!==void 0}const B=w({create(n){const e=(r,s)=>{const i=R(r,s);i!==void 0&&n.report({fix(p){const o=c(r),u=p.replaceText(s,`${i}(${n.sourceCode.getText(s)})`);if(o===void 0||d(o).has("read"))return[u];const f=O(p,o);return f===void 0?[u]:[u,f]},messageId:"enabledReturnsSource",node:s})},t=r=>{if(r.async){n.report({messageId:"enabledIsAsync",node:r});return}if(r.type!==a.ArrowFunctionExpression)return;const{body:s}=r;if(s.type===a.MemberExpression){e(r,s);return}s.type===a.Identifier&&!C(r,s)&&n.report({messageId:"enabledReturnsBareSource",node:s})};return{Property(r){m(r)!=="enabled"||!h(r.value)||N(r,r.value)&&t(r.value)}}},meta:{docs:{description:"A contribution `enabled` predicate answers the visibility fact, not the source that carries it."},fixable:"code",messages:{enabledIsAsync:"A contribution `enabled` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).",enabledReturnsBareSource:"A contribution `enabled` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).",enabledReturnsSource:"A contribution `enabled` predicate returns the source instead of the fact; read it with `read(...)` (D220)."},schema:[],type:"problem"},name:"enabled-predicate"});export{B as enabledPredicate};
|
|
2
|
+
//# sourceMappingURL=enabled-predicate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enabled-predicate.js","sources":["../../src/rules/enabled-predicate.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport type { FunctionLike } from '../ast';\nimport { calleeName, isFunctionLike, memberFirstStep, memberRoot, patternKeys, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'enabledIsAsync' | 'enabledReturnsBareSource' | 'enabledReturnsSource';\n\nconst CONTRIBUTION_BUILDERS = new Set(['pipe', 'register', 'slot']);\nconst CONTEXT_KEYS = new Set(['exports', 'imports', 'own', 'read']);\nconst CONTEXT_SOURCES = new Set(['exports', 'imports', 'own']);\n\n/** The options object of `slot(target, contribution, {…})`, `pipe(target, {fold}, {…})`, `register(…)`. */\nfunction isBuilderOptions(node: TSESTree.ObjectExpression): boolean {\n const { parent } = node;\n\n if (parent.type !== AST_NODE_TYPES.CallExpression || parent.arguments.length < 2) return false;\n\n const name = calleeName(parent);\n\n return name !== undefined && CONTRIBUTION_BUILDERS.has(name) && parent.arguments.at(-1) === node;\n}\n\n/** The destructured evaluation context of a contribution predicate: `({ exports, imports, own, read })`. */\nfunction contextPattern(fn: FunctionLike): TSESTree.ObjectPattern | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.ObjectPattern) return undefined;\n\n return [...patternKeys(parameter)].some(key => CONTEXT_KEYS.has(key)) ? parameter : undefined;\n}\n\n/** The same context taken as one named parameter: `context => context.imports.platform.submitAllowed`. */\nfunction contextIdentifier(fn: FunctionLike): TSESTree.Identifier | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return parameter;\n}\n\nfunction isNamedContextAccess(parameter: TSESTree.Identifier, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n const step = memberFirstStep(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier || root.name !== parameter.name) return false;\n\n return step !== undefined && CONTEXT_SOURCES.has(step);\n}\n\nfunction isDestructuredContextAccess(pattern: TSESTree.ObjectPattern, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier) return false;\n\n return CONTEXT_SOURCES.has(root.name) && patternKeys(pattern).has(root.name);\n}\n\n/** The reader a fix would call, or `undefined` when the body reaches something other than the instance context. */\nfunction readerFor(fn: FunctionLike, body: TSESTree.MemberExpression): string | undefined {\n const pattern = contextPattern(fn);\n\n if (pattern !== undefined) return isDestructuredContextAccess(pattern, body) ? 'read' : undefined;\n\n const parameter = contextIdentifier(fn);\n\n if (parameter === undefined) return undefined;\n\n return isNamedContextAccess(parameter, body) ? `${parameter.name}.read` : undefined;\n}\n\n/** `read` joins the destructured context in place, so the fixed predicate keeps the order authors write. */\nfunction insertReadKey(fixer: TSESLint.RuleFixer, pattern: TSESTree.ObjectPattern): TSESLint.RuleFix | undefined {\n const successor = pattern.properties.find(property => {\n const name = propertyName(property);\n\n return name === undefined || name > 'read';\n });\n\n if (successor !== undefined) return fixer.insertTextBefore(successor, 'read, ');\n\n const last = pattern.properties.at(-1);\n\n return last === undefined ? undefined : fixer.insertTextAfter(last, ', read');\n}\n\nfunction isOwnParameter(fn: FunctionLike, identifier: TSESTree.Identifier): boolean {\n return fn.params.some(\n parameter => parameter.type === AST_NODE_TYPES.Identifier && parameter.name === identifier.name,\n );\n}\n\nfunction isContributionEnabled(property: TSESTree.Property, fn: FunctionLike): boolean {\n const { parent } = property;\n\n return (\n (parent.type === AST_NODE_TYPES.ObjectExpression && isBuilderOptions(parent)) || contextPattern(fn) !== undefined\n );\n}\n\nconst enabledPredicate = createRule<[], MessageIds>({\n create(context) {\n const reportSource = (fn: FunctionLike, body: TSESTree.MemberExpression): void => {\n const reader = readerFor(fn, body);\n\n if (reader === undefined) return;\n\n context.report({\n fix(fixer) {\n const pattern = contextPattern(fn);\n const wrapped = fixer.replaceText(body, `${reader}(${context.sourceCode.getText(body)})`);\n\n if (pattern === undefined || patternKeys(pattern).has('read')) return [wrapped];\n\n const inserted = insertReadKey(fixer, pattern);\n\n return inserted === undefined ? [wrapped] : [wrapped, inserted];\n },\n messageId: 'enabledReturnsSource',\n node: body,\n });\n };\n\n const check = (fn: FunctionLike): void => {\n if (fn.async) {\n context.report({ messageId: 'enabledIsAsync', node: fn });\n\n return;\n }\n\n if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression) return;\n\n const { body } = fn;\n\n if (body.type === AST_NODE_TYPES.MemberExpression) {\n reportSource(fn, body);\n\n return;\n }\n\n if (body.type === AST_NODE_TYPES.Identifier && !isOwnParameter(fn, body)) {\n context.report({ messageId: 'enabledReturnsBareSource', node: body });\n }\n };\n\n return {\n Property(node): void {\n if (propertyName(node) !== 'enabled' || !isFunctionLike(node.value)) return;\n\n if (isContributionEnabled(node, node.value)) check(node.value);\n },\n };\n },\n meta: {\n docs: {\n description: 'A contribution `enabled` predicate answers the visibility fact, not the source that carries it.',\n },\n fixable: 'code',\n messages: {\n enabledIsAsync:\n 'A contribution `enabled` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).',\n enabledReturnsBareSource:\n 'A contribution `enabled` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).',\n enabledReturnsSource:\n 'A contribution `enabled` predicate returns the source instead of the fact; read it with `read(...)` (D220).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'enabled-predicate',\n});\n\nexport { enabledPredicate };\n"],"names":["CONTRIBUTION_BUILDERS","CONTEXT_KEYS","CONTEXT_SOURCES","isBuilderOptions","node","parent","AST_NODE_TYPES","name","calleeName","contextPattern","fn","parameter","patternKeys","key","contextIdentifier","isNamedContextAccess","body","root","memberRoot","step","memberFirstStep","isDestructuredContextAccess","pattern","readerFor","insertReadKey","fixer","successor","property","propertyName","last","isOwnParameter","identifier","isContributionEnabled","enabledPredicate","createRule","context","reportSource","reader","wrapped","inserted","check","isFunctionLike"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AST_NODE_TYPES as x}from"@typescript-eslint/utils";import{isFunctionLike as k}from"../ast.js";import{classifyDeclaration as y,bindingAt as v,usageIn as D,STATUS_MEMBERS as E}from"../command-hooks.js";import{apiName as I,unwrap as h}from"../model-bindings.js";import{createRule as S}from"../rule.js";const T=new Set(["useCallback","useEffect","useImperativeHandle","useInsertionEffect","useLayoutEffect","useMemo"]),R=[...E,"run"],M=2;function A(n){const e=n.arguments.at(-1);if(!(n.arguments.length<M||e?.type!==x.ArrayExpression))return{callback:n.arguments.slice(0,-1).findLast(k),deps:e}}function w(n){const e=[];for(const t of R)for(const o of n)o.member===t&&!e.includes(o.text)&&e.push(o.text);return e}function C(n){const[e]=n.accesses;if(!(n.opaque||e?.member!=="run"||e.optional))return n.accesses.every(t=>t.text===e.text)?e.text:void 0}function O(n,e,t){if(e.opaque)return[];const o=w(e.accesses);return n.proved&&!e.accesses.some(c=>c.member==="run")?[...o,`${t}.run`]:o}function P(n,e){const t=n.elements.findIndex(r=>r===e),o=n.elements[t+1]??null,c=n.elements[t-1]??null;return o!==null?[e.range[0],o.range[0]]:c===null?e.range:[c.range[1],e.range[1]]}function g(n,e,t,o){const c=e.elements.some(r=>r!==null&&r!==t&&n.getText(h(r))===o);return r=>c?r.removeRange(P(e,t)):r.replaceText(t,o)}function _(n,e){const t=I(n,e.callee,"react");return t!==void 0&&T.has(t)?A(e):void 0}const j=S({create(n){const e=n.sourceCode,t=new Map,o=[],c=[],r=(s,a,d)=>{const i=e.getText(h(a)),f=D(e,d,s.callback),u=d.record?"commandRecordInDeps":"commandHookInDeps",m=C(f);if(m!==void 0){n.report({data:{name:i},fix:g(e,s.deps,a,m),messageId:u,node:a});return}const l=O(d,f,i).map(p=>({data:{path:p},fix:g(e,s.deps,a,p),messageId:"dependOnMember"}));l.length===0?n.report({data:{name:i},messageId:u,node:a}):n.report({data:{name:i},messageId:u,node:a,suggest:l})},b=s=>{const a=_(e,s);if(a!==void 0)for(const d of a.deps.elements){if(d===null)continue;const i=v(e,t,d);i!==void 0&&r(a,d,i)}};return{CallExpression(s){c.push(s)},VariableDeclarator(s){o.push(s)},"Program:exit"(){for(const s of o)y(e,s,t);for(const s of c)b(s)}}},meta:{docs:{description:"A React dependency array names the stable `run` of a command hook, not the hook object."},fixable:"code",hasSuggestions:!0,messages:{commandHookInDeps:"The hook object of a command is rebuilt on every `inFlight`/`outcome` change, so this dependency re-runs the effect or voids the memo on each of them, and an effect that calls `run` from it loops. Depend on `{{name}}.run` \u2014 stable for the life of the binding \u2014 or on the status field the callback reads (D290).",commandRecordInDeps:"`{{name}}` is the record `
|
|
1
|
+
import{AST_NODE_TYPES as x}from"@typescript-eslint/utils";import{isFunctionLike as k}from"../ast.js";import{classifyDeclaration as y,bindingAt as v,usageIn as D,STATUS_MEMBERS as E}from"../command-hooks.js";import{apiName as I,unwrap as h}from"../model-bindings.js";import{createRule as S}from"../rule.js";const T=new Set(["useCallback","useEffect","useImperativeHandle","useInsertionEffect","useLayoutEffect","useMemo"]),R=[...E,"run"],M=2;function A(n){const e=n.arguments.at(-1);if(!(n.arguments.length<M||e?.type!==x.ArrayExpression))return{callback:n.arguments.slice(0,-1).findLast(k),deps:e}}function w(n){const e=[];for(const t of R)for(const o of n)o.member===t&&!e.includes(o.text)&&e.push(o.text);return e}function C(n){const[e]=n.accesses;if(!(n.opaque||e?.member!=="run"||e.optional))return n.accesses.every(t=>t.text===e.text)?e.text:void 0}function O(n,e,t){if(e.opaque)return[];const o=w(e.accesses);return n.proved&&!e.accesses.some(c=>c.member==="run")?[...o,`${t}.run`]:o}function P(n,e){const t=n.elements.findIndex(r=>r===e),o=n.elements[t+1]??null,c=n.elements[t-1]??null;return o!==null?[e.range[0],o.range[0]]:c===null?e.range:[c.range[1],e.range[1]]}function g(n,e,t,o){const c=e.elements.some(r=>r!==null&&r!==t&&n.getText(h(r))===o);return r=>c?r.removeRange(P(e,t)):r.replaceText(t,o)}function _(n,e){const t=I(n,e.callee,"react");return t!==void 0&&T.has(t)?A(e):void 0}const j=S({create(n){const e=n.sourceCode,t=new Map,o=[],c=[],r=(s,a,d)=>{const i=e.getText(h(a)),f=D(e,d,s.callback),u=d.record?"commandRecordInDeps":"commandHookInDeps",m=C(f);if(m!==void 0){n.report({data:{name:i},fix:g(e,s.deps,a,m),messageId:u,node:a});return}const l=O(d,f,i).map(p=>({data:{path:p},fix:g(e,s.deps,a,p),messageId:"dependOnMember"}));l.length===0?n.report({data:{name:i},messageId:u,node:a}):n.report({data:{name:i},messageId:u,node:a,suggest:l})},b=s=>{const a=_(e,s);if(a!==void 0)for(const d of a.deps.elements){if(d===null)continue;const i=v(e,t,d);i!==void 0&&r(a,d,i)}};return{CallExpression(s){c.push(s)},VariableDeclarator(s){o.push(s)},"Program:exit"(){for(const s of o)y(e,s,t);for(const s of c)b(s)}}},meta:{docs:{description:"A React dependency array names the stable `run` of a command hook, not the hook object."},fixable:"code",hasSuggestions:!0,messages:{commandHookInDeps:"The hook object of a command is rebuilt on every `inFlight`/`outcome` change, so this dependency re-runs the effect or voids the memo on each of them, and an effect that calls `run` from it loops. Depend on `{{name}}.run` \u2014 stable for the life of the binding \u2014 or on the status field the callback reads (D290).",commandRecordInDeps:"`{{name}}` is the record `useModel` returns for a selection, and it is a new object on every render, so this dependency changes on every render: the effect re-runs, the memo is voided, and an effect that calls `run` from it never settles. Depend on the fields the callback uses, such as `hooks.save.run` (D290).",dependOnMember:"Depend on `{{path}}` instead of the object this element names."},schema:[],type:"problem"},name:"no-command-in-deps"});export{j as noCommandInDeps};
|
|
2
2
|
//# sourceMappingURL=no-command-in-deps.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-command-in-deps.js","sources":["../../src/rules/no-command-in-deps.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { isFunctionLike } from '../ast';\nimport type { Access, Binding, HookKinds, Usage } from '../command-hooks';\nimport { bindingAt, classifyDeclaration, STATUS_MEMBERS, usageIn } from '../command-hooks';\nimport { apiName, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'commandHookInDeps' | 'commandRecordInDeps' | 'dependOnMember';\ntype Site = { readonly callback: TSESTree.Node | undefined; readonly deps: TSESTree.ArrayExpression };\n\nconst REACT_HOOKS = new Set([\n 'useCallback',\n 'useEffect',\n 'useImperativeHandle',\n 'useInsertionEffect',\n 'useLayoutEffect',\n 'useMemo',\n]);\n/** The order a suggestion list is read in: what the callback already watches, and the stable member last. */\nconst MEMBERS = [...STATUS_MEMBERS, 'run'];\n/** A dependency array is an argument, and the callback that reads it is the argument before. */\nconst SITE_ARITY = 2;\n\n/** `useEffect(fn, deps)` and `useImperativeHandle(ref, create, deps)`: the array last, the callback before it. */\nfunction dependencySite(node: TSESTree.CallExpression): Site | undefined {\n const deps = node.arguments.at(-1);\n\n if (node.arguments.length < SITE_ARITY || deps?.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n return { callback: node.arguments.slice(0, -1).findLast(isFunctionLike), deps };\n}\n\n/** Each path the callback wrote once, statuses before `run`: what the author already reads comes first. */\nfunction readPaths(accesses: readonly Access[]): readonly string[] {\n const paths: string[] = [];\n\n for (const member of MEMBERS) {\n for (const access of accesses) {\n if (access.member === member && !paths.includes(access.text)) paths.push(access.text);\n }\n }\n\n return paths;\n}\n\n/** One answer and no other: every read of the binding went through the same `run`, written without a `?.`. */\nfunction fixedPath(usage: Usage): string | undefined {\n const [first] = usage.accesses;\n\n if (usage.opaque || first?.member !== 'run' || first.optional) return undefined;\n\n return usage.accesses.every(access => access.text === first.text) ? first.text : undefined;\n}\n\n/**\n * Oxlint applies the first suggestion unattended, so a use no member replaces — the object passed on, destructured,\n * or read past the members named here — carries none: the report says what is wrong, and the author says what the\n * dependency meant. A selection field proved by `run` keeps that offer even where this callback only reads a status.\n */\nfunction suggestedPaths(binding: Binding, usage: Usage, base: string): readonly string[] {\n if (usage.opaque) return [];\n\n const paths = readPaths(usage.accesses);\n\n return binding.proved && !usage.accesses.some(access => access.member === 'run') ? [...paths, `${base}.run`] : paths;\n}\n\n/** The element and one comma with it, for an array that already names the dependency the fix would write. */\nfunction removalRange(deps: TSESTree.ArrayExpression, element: TSESTree.Node): TSESTree.Range {\n const index = deps.elements.findIndex(other => other === element);\n const next = deps.elements[index + 1] ?? null;\n const previous = deps.elements[index - 1] ?? null;\n\n if (next !== null) return [element.range[0], next.range[0]];\n\n return previous === null ? element.range : [previous.range[1], element.range[1]];\n}\n\n/** A dependency already written stays the one written: the element is removed rather than doubled. */\nfunction rewrite(\n source: TSESLint.SourceCode,\n deps: TSESTree.ArrayExpression,\n element: TSESTree.Node,\n path: string,\n): TSESLint.ReportFixFunction {\n const duplicate = deps.elements.some(\n other => other !== null && other !== element && source.getText(unwrap(other)) === path,\n );\n\n return fixer => (duplicate ? fixer.removeRange(removalRange(deps, element)) : fixer.replaceText(element, path));\n}\n\n/** The dependency site of one of React's own hooks, under whatever local name this file gave the import. */\nfunction reactSite(source: TSESLint.SourceCode, node: TSESTree.CallExpression): Site | undefined {\n const name = apiName(source, node.callee, 'react');\n\n return name !== undefined && REACT_HOOKS.has(name) ? dependencySite(node) : undefined;\n}\n\nconst noCommandInDeps = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const kinds: HookKinds = new Map();\n const declarators: TSESTree.VariableDeclarator[] = [];\n const calls: TSESTree.CallExpression[] = [];\n\n /** The name is read off the unwrapped element, so an `as`, a `satisfies` or a `!` never reaches a fix. */\n const report = (site: Site, element: TSESTree.Node, binding: Binding): void => {\n const base = source.getText(unwrap(element));\n const usage = usageIn(source, binding, site.callback);\n const messageId = binding.record ? 'commandRecordInDeps' : 'commandHookInDeps';\n const path = fixedPath(usage);\n\n if (path !== undefined) {\n context.report({\n data: { name: base },\n fix: rewrite(source, site.deps, element, path),\n messageId,\n node: element,\n });\n\n return;\n }\n\n const suggest = suggestedPaths(binding, usage, base).map(suggestion => ({\n data: { path: suggestion },\n fix: rewrite(source, site.deps, element, suggestion),\n messageId: 'dependOnMember' as const,\n }));\n\n if (suggest.length === 0) context.report({ data: { name: base }, messageId, node: element });\n else context.report({ data: { name: base }, messageId, node: element, suggest });\n };\n\n const check = (call: TSESTree.CallExpression): void => {\n const site = reactSite(source, call);\n\n if (site === undefined) return;\n\n for (const element of site.deps.elements) {\n if (element === null) continue;\n\n const binding = bindingAt(source, kinds, element);\n\n if (binding !== undefined) report(site, element, binding);\n }\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n VariableDeclarator(node): void {\n declarators.push(node);\n },\n 'Program:exit'(): void {\n for (const declarator of declarators) classifyDeclaration(source, declarator, kinds);\n for (const call of calls) check(call);\n },\n };\n },\n meta: {\n docs: {\n description: 'A React dependency array names the stable `run` of a command hook, not the hook object.',\n },\n fixable: 'code',\n hasSuggestions: true,\n messages: {\n commandHookInDeps:\n 'The hook object of a command is rebuilt on every `inFlight`/`outcome` change, so this dependency re-runs the effect or voids the memo on each of them, and an effect that calls `run` from it loops. Depend on `{{name}}.run` — stable for the life of the binding — or on the status field the callback reads (D290).',\n commandRecordInDeps:\n '`{{name}}` is the record `useCommands` returns — as does `useModel` with a selection — and it is a new object on every render, so this dependency changes on every render: the effect re-runs, the memo is voided, and an effect that calls `run` from it never settles. Depend on the fields the callback uses, such as `hooks.save.run` (D290).',\n dependOnMember: 'Depend on `{{path}}` instead of the object this element names.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-command-in-deps',\n});\n\nexport { noCommandInDeps };\n"],"names":["REACT_HOOKS","MEMBERS","STATUS_MEMBERS","SITE_ARITY","dependencySite","node","deps","AST_NODE_TYPES","isFunctionLike","readPaths","accesses","paths","member","access","fixedPath","usage","first","suggestedPaths","binding","base","removalRange","element","index","other","next","previous","rewrite","source","path","duplicate","unwrap","fixer","reactSite","name","apiName","noCommandInDeps","createRule","context","kinds","declarators","calls","report","site","usageIn","messageId","suggest","suggestion","check","call","bindingAt","declarator","classifyDeclaration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"no-command-in-deps.js","sources":["../../src/rules/no-command-in-deps.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { isFunctionLike } from '../ast';\nimport type { Access, Binding, HookKinds, Usage } from '../command-hooks';\nimport { bindingAt, classifyDeclaration, STATUS_MEMBERS, usageIn } from '../command-hooks';\nimport { apiName, unwrap } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'commandHookInDeps' | 'commandRecordInDeps' | 'dependOnMember';\ntype Site = { readonly callback: TSESTree.Node | undefined; readonly deps: TSESTree.ArrayExpression };\n\nconst REACT_HOOKS = new Set([\n 'useCallback',\n 'useEffect',\n 'useImperativeHandle',\n 'useInsertionEffect',\n 'useLayoutEffect',\n 'useMemo',\n]);\n/** The order a suggestion list is read in: what the callback already watches, and the stable member last. */\nconst MEMBERS = [...STATUS_MEMBERS, 'run'];\n/** A dependency array is an argument, and the callback that reads it is the argument before. */\nconst SITE_ARITY = 2;\n\n/** `useEffect(fn, deps)` and `useImperativeHandle(ref, create, deps)`: the array last, the callback before it. */\nfunction dependencySite(node: TSESTree.CallExpression): Site | undefined {\n const deps = node.arguments.at(-1);\n\n if (node.arguments.length < SITE_ARITY || deps?.type !== AST_NODE_TYPES.ArrayExpression) return undefined;\n\n return { callback: node.arguments.slice(0, -1).findLast(isFunctionLike), deps };\n}\n\n/** Each path the callback wrote once, statuses before `run`: what the author already reads comes first. */\nfunction readPaths(accesses: readonly Access[]): readonly string[] {\n const paths: string[] = [];\n\n for (const member of MEMBERS) {\n for (const access of accesses) {\n if (access.member === member && !paths.includes(access.text)) paths.push(access.text);\n }\n }\n\n return paths;\n}\n\n/** One answer and no other: every read of the binding went through the same `run`, written without a `?.`. */\nfunction fixedPath(usage: Usage): string | undefined {\n const [first] = usage.accesses;\n\n if (usage.opaque || first?.member !== 'run' || first.optional) return undefined;\n\n return usage.accesses.every(access => access.text === first.text) ? first.text : undefined;\n}\n\n/**\n * Oxlint applies the first suggestion unattended, so a use no member replaces — the object passed on, destructured,\n * or read past the members named here — carries none: the report says what is wrong, and the author says what the\n * dependency meant. A selection field proved by `run` keeps that offer even where this callback only reads a status.\n */\nfunction suggestedPaths(binding: Binding, usage: Usage, base: string): readonly string[] {\n if (usage.opaque) return [];\n\n const paths = readPaths(usage.accesses);\n\n return binding.proved && !usage.accesses.some(access => access.member === 'run') ? [...paths, `${base}.run`] : paths;\n}\n\n/** The element and one comma with it, for an array that already names the dependency the fix would write. */\nfunction removalRange(deps: TSESTree.ArrayExpression, element: TSESTree.Node): TSESTree.Range {\n const index = deps.elements.findIndex(other => other === element);\n const next = deps.elements[index + 1] ?? null;\n const previous = deps.elements[index - 1] ?? null;\n\n if (next !== null) return [element.range[0], next.range[0]];\n\n return previous === null ? element.range : [previous.range[1], element.range[1]];\n}\n\n/** A dependency already written stays the one written: the element is removed rather than doubled. */\nfunction rewrite(\n source: TSESLint.SourceCode,\n deps: TSESTree.ArrayExpression,\n element: TSESTree.Node,\n path: string,\n): TSESLint.ReportFixFunction {\n const duplicate = deps.elements.some(\n other => other !== null && other !== element && source.getText(unwrap(other)) === path,\n );\n\n return fixer => (duplicate ? fixer.removeRange(removalRange(deps, element)) : fixer.replaceText(element, path));\n}\n\n/** The dependency site of one of React's own hooks, under whatever local name this file gave the import. */\nfunction reactSite(source: TSESLint.SourceCode, node: TSESTree.CallExpression): Site | undefined {\n const name = apiName(source, node.callee, 'react');\n\n return name !== undefined && REACT_HOOKS.has(name) ? dependencySite(node) : undefined;\n}\n\nconst noCommandInDeps = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const kinds: HookKinds = new Map();\n const declarators: TSESTree.VariableDeclarator[] = [];\n const calls: TSESTree.CallExpression[] = [];\n\n /** The name is read off the unwrapped element, so an `as`, a `satisfies` or a `!` never reaches a fix. */\n const report = (site: Site, element: TSESTree.Node, binding: Binding): void => {\n const base = source.getText(unwrap(element));\n const usage = usageIn(source, binding, site.callback);\n const messageId = binding.record ? 'commandRecordInDeps' : 'commandHookInDeps';\n const path = fixedPath(usage);\n\n if (path !== undefined) {\n context.report({\n data: { name: base },\n fix: rewrite(source, site.deps, element, path),\n messageId,\n node: element,\n });\n\n return;\n }\n\n const suggest = suggestedPaths(binding, usage, base).map(suggestion => ({\n data: { path: suggestion },\n fix: rewrite(source, site.deps, element, suggestion),\n messageId: 'dependOnMember' as const,\n }));\n\n if (suggest.length === 0) context.report({ data: { name: base }, messageId, node: element });\n else context.report({ data: { name: base }, messageId, node: element, suggest });\n };\n\n const check = (call: TSESTree.CallExpression): void => {\n const site = reactSite(source, call);\n\n if (site === undefined) return;\n\n for (const element of site.deps.elements) {\n if (element === null) continue;\n\n const binding = bindingAt(source, kinds, element);\n\n if (binding !== undefined) report(site, element, binding);\n }\n };\n\n return {\n CallExpression(node): void {\n calls.push(node);\n },\n VariableDeclarator(node): void {\n declarators.push(node);\n },\n 'Program:exit'(): void {\n for (const declarator of declarators) classifyDeclaration(source, declarator, kinds);\n for (const call of calls) check(call);\n },\n };\n },\n meta: {\n docs: {\n description: 'A React dependency array names the stable `run` of a command hook, not the hook object.',\n },\n fixable: 'code',\n hasSuggestions: true,\n messages: {\n commandHookInDeps:\n 'The hook object of a command is rebuilt on every `inFlight`/`outcome` change, so this dependency re-runs the effect or voids the memo on each of them, and an effect that calls `run` from it loops. Depend on `{{name}}.run` — stable for the life of the binding — or on the status field the callback reads (D290).',\n commandRecordInDeps:\n '`{{name}}` is the record `useModel` returns for a selection, and it is a new object on every render, so this dependency changes on every render: the effect re-runs, the memo is voided, and an effect that calls `run` from it never settles. Depend on the fields the callback uses, such as `hooks.save.run` (D290).',\n dependOnMember: 'Depend on `{{path}}` instead of the object this element names.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-command-in-deps',\n});\n\nexport { noCommandInDeps };\n"],"names":["REACT_HOOKS","MEMBERS","STATUS_MEMBERS","SITE_ARITY","dependencySite","node","deps","AST_NODE_TYPES","isFunctionLike","readPaths","accesses","paths","member","access","fixedPath","usage","first","suggestedPaths","binding","base","removalRange","element","index","other","next","previous","rewrite","source","path","duplicate","unwrap","fixer","reactSite","name","apiName","noCommandInDeps","createRule","context","kinds","declarators","calls","report","site","usageIn","messageId","suggest","suggestion","check","call","bindingAt","declarator","classifyDeclaration"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AST_NODE_TYPES as i,ASTUtils as a}from"@typescript-eslint/utils";import{createRule as l}from"../rule.js";const s=/^@opetope\/[^/]+\/internal(?:\/|$)/;function n(r){if(r.type===i.TSLiteralType)return n(r.literal);if(r.type===i.Literal&&typeof r.value=="string")return r.value;if(r.type===i.TemplateLiteral&&r.expressions.length===0)return r.quasis[0]?.value.cooked}function o(r){return r.argument??r.source??null}const p=l({create(r){const t=e=>{e!==null&&s.test(n(e)??"")&&r.report({messageId:"internalImport",node:e})};return{CallExpression(e){e.callee.type!==i.Identifier||e.callee.name!=="require"||(a.findVariable(r.sourceCode.getScope(e),e.callee)?.defs.length??0)>0||t(e.arguments[0]??null)},ExportAllDeclaration:e=>t(e.source),ExportNamedDeclaration:e=>t(e.source),ImportDeclaration:e=>t(e.source),ImportExpression:e=>t(e.source),TSExternalModuleReference:e=>t(e.expression),TSImportType:e=>t(o(e))}},meta:{docs:{description:"Application code and tests use public Opetope entries instead of the internal ABI."},messages:{internalImport:"Opetope internal entries are reserved for library implementation and explicit host integration. Use public entries; tests run real
|
|
1
|
+
import{AST_NODE_TYPES as i,ASTUtils as a}from"@typescript-eslint/utils";import{createRule as l}from"../rule.js";const s=/^@opetope\/[^/]+\/internal(?:\/|$)/;function n(r){if(r.type===i.TSLiteralType)return n(r.literal);if(r.type===i.Literal&&typeof r.value=="string")return r.value;if(r.type===i.TemplateLiteral&&r.expressions.length===0)return r.quasis[0]?.value.cooked}function o(r){return r.argument??r.source??null}const p=l({create(r){const t=e=>{e!==null&&s.test(n(e)??"")&&r.report({messageId:"internalImport",node:e})};return{CallExpression(e){e.callee.type!==i.Identifier||e.callee.name!=="require"||(a.findVariable(r.sourceCode.getScope(e),e.callee)?.defs.length??0)>0||t(e.arguments[0]??null)},ExportAllDeclaration:e=>t(e.source),ExportNamedDeclaration:e=>t(e.source),ImportDeclaration:e=>t(e.source),ImportExpression:e=>t(e.source),TSExternalModuleReference:e=>t(e.expression),TSImportType:e=>t(o(e))}},meta:{docs:{description:"Application code and tests use public Opetope entries instead of the internal ABI."},messages:{internalImport:"Opetope internal entries are reserved for library implementation and explicit host integration. Use public entries; tests run real Commands with runCommand from @opetope/react/testing."},schema:[],type:"problem"},name:"no-internal-imports"});export{p as noInternalImports};
|
|
2
2
|
//# sourceMappingURL=no-internal-imports.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-internal-imports.js","sources":["../../src/rules/no-internal-imports.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';\n\nimport { createRule } from '../rule';\n\n/** Match the package boundary, including deep paths, without claiming unrelated packages or `internals`. */\nconst internalEntry = /^@opetope\\/[^/]+\\/internal(?:\\/|$)/;\n\nfunction sourceText(node: TSESTree.Node): string | null | undefined {\n if (node.type === AST_NODE_TYPES.TSLiteralType) return sourceText(node.literal);\n if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string') return node.value;\n if (node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0)\n return node.quasis[0]?.value.cooked;\n return undefined;\n}\n\n/** TypeScript-ESLint calls this `argument`; Oxlint's ESTree bridge exposes the same source as `source`. */\nfunction importTypeSource(node: {\n readonly argument?: TSESTree.Node;\n readonly source?: TSESTree.Node;\n}): TSESTree.Node | null {\n return node.argument ?? node.source ?? null;\n}\n\nconst noInternalImports = createRule<[], 'internalImport'>({\n create(context) {\n const inspect = (node: TSESTree.Node | null): void => {\n if (node !== null && internalEntry.test(sourceText(node) ?? ''))\n context.report({ messageId: 'internalImport', node });\n };\n return {\n CallExpression(node): void {\n if (node.callee.type !== AST_NODE_TYPES.Identifier || node.callee.name !== 'require') return;\n const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node.callee);\n if ((variable?.defs.length ?? 0) > 0) return;\n inspect(node.arguments[0] ?? null);\n },\n ExportAllDeclaration: node => inspect(node.source),\n ExportNamedDeclaration: node => inspect(node.source),\n ImportDeclaration: node => inspect(node.source),\n ImportExpression: node => inspect(node.source),\n TSExternalModuleReference: node => inspect(node.expression),\n TSImportType: node => inspect(importTypeSource(node)),\n };\n },\n meta: {\n docs: { description: 'Application code and tests use public Opetope entries instead of the internal ABI.' },\n messages: {\n internalImport:\n 'Opetope internal entries are reserved for library implementation and explicit host integration. Use public entries; tests run real
|
|
1
|
+
{"version":3,"file":"no-internal-imports.js","sources":["../../src/rules/no-internal-imports.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';\n\nimport { createRule } from '../rule';\n\n/** Match the package boundary, including deep paths, without claiming unrelated packages or `internals`. */\nconst internalEntry = /^@opetope\\/[^/]+\\/internal(?:\\/|$)/;\n\nfunction sourceText(node: TSESTree.Node): string | null | undefined {\n if (node.type === AST_NODE_TYPES.TSLiteralType) return sourceText(node.literal);\n if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string') return node.value;\n if (node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0)\n return node.quasis[0]?.value.cooked;\n return undefined;\n}\n\n/** TypeScript-ESLint calls this `argument`; Oxlint's ESTree bridge exposes the same source as `source`. */\nfunction importTypeSource(node: {\n readonly argument?: TSESTree.Node;\n readonly source?: TSESTree.Node;\n}): TSESTree.Node | null {\n return node.argument ?? node.source ?? null;\n}\n\nconst noInternalImports = createRule<[], 'internalImport'>({\n create(context) {\n const inspect = (node: TSESTree.Node | null): void => {\n if (node !== null && internalEntry.test(sourceText(node) ?? ''))\n context.report({ messageId: 'internalImport', node });\n };\n return {\n CallExpression(node): void {\n if (node.callee.type !== AST_NODE_TYPES.Identifier || node.callee.name !== 'require') return;\n const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node.callee);\n if ((variable?.defs.length ?? 0) > 0) return;\n inspect(node.arguments[0] ?? null);\n },\n ExportAllDeclaration: node => inspect(node.source),\n ExportNamedDeclaration: node => inspect(node.source),\n ImportDeclaration: node => inspect(node.source),\n ImportExpression: node => inspect(node.source),\n TSExternalModuleReference: node => inspect(node.expression),\n TSImportType: node => inspect(importTypeSource(node)),\n };\n },\n meta: {\n docs: { description: 'Application code and tests use public Opetope entries instead of the internal ABI.' },\n messages: {\n internalImport:\n 'Opetope internal entries are reserved for library implementation and explicit host integration. Use public entries; tests run real Commands with runCommand from @opetope/react/testing.',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-internal-imports',\n});\n\nexport { noInternalImports };\n"],"names":["internalEntry","sourceText","node","AST_NODE_TYPES","importTypeSource","noInternalImports","createRule","context","inspect","ASTUtils"],"mappings":"gHAMA,MAAMA,EAAgB,qCAEtB,SAASC,EAAWC,EAAmB,CACrC,GAAIA,EAAK,OAASC,EAAe,cAAe,OAAOF,EAAWC,EAAK,OAAO,EAC9E,GAAIA,EAAK,OAASC,EAAe,SAAW,OAAOD,EAAK,OAAU,SAAU,OAAOA,EAAK,MACxF,GAAIA,EAAK,OAASC,EAAe,iBAAmBD,EAAK,YAAY,SAAW,EAC9E,OAAOA,EAAK,OAAO,CAAC,GAAG,MAAM,MAEjC,CAGA,SAASE,EAAiBF,EAGzB,CACC,OAAOA,EAAK,UAAYA,EAAK,QAAU,IACzC,CAEA,MAAMG,EAAoBC,EAAiC,CACzD,OAAOC,EAAO,CACZ,MAAMC,EAAWN,GAAoC,CAC/CA,IAAS,MAAQF,EAAc,KAAKC,EAAWC,CAAI,GAAK,EAAE,GAC5DK,EAAQ,OAAO,CAAE,UAAW,iBAAkB,KAAAL,CAAI,CAAE,CACxD,EACA,MAAO,CACL,eAAeA,EAAI,CACbA,EAAK,OAAO,OAASC,EAAe,YAAcD,EAAK,OAAO,OAAS,YAC1DO,EAAS,aAAaF,EAAQ,WAAW,SAASL,CAAI,EAAGA,EAAK,MAAM,GACtE,KAAK,QAAU,GAAK,GACnCM,EAAQN,EAAK,UAAU,CAAC,GAAK,IAAI,CACnC,EACA,qBAAsBA,GAAQM,EAAQN,EAAK,MAAM,EACjD,uBAAwBA,GAAQM,EAAQN,EAAK,MAAM,EACnD,kBAAmBA,GAAQM,EAAQN,EAAK,MAAM,EAC9C,iBAAkBA,GAAQM,EAAQN,EAAK,MAAM,EAC7C,0BAA2BA,GAAQM,EAAQN,EAAK,UAAU,EAC1D,aAAcA,GAAQM,EAAQJ,EAAiBF,CAAI,CAAC,EAExD,EACA,KAAM,CACJ,KAAM,CAAE,YAAa,oFAAoF,EACzG,SAAU,CACR,eACE,0LACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,qBACP,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
type MessageIds = 'commandLane' | 'commandLatest' | 'commandName' | 'commandOnce' | 'commandOutcomeStatus' | 'commandPolicy' | 'commandQueueBy' | 'conditionFrom' | 'contextCall' | 'contextCalls' | 'contributionWhen' | 'deliveryKey' | 'deliveryKind' | 'deliveryReject' | 'devtoolsResourceActivity' | 'effectOnDispose' | 'effectSkipInitial' | 'effectWhen' | 'eventBackpressure' | 'fromExternal' | 'loadingActivity' | 'navigatorPolicy' | 'resourceDisposer' | 'resourceInvalidate' | 'runtimeInternalEntry' | 'scopeKeyed' | 'useCommands';
|
|
2
|
+
declare const noRetiredVocabulary: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export { noRetiredVocabulary };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AST_NODE_TYPES as s}from"@typescript-eslint/utils";import{propertyName as u,findProperty as O}from"../ast.js";import{methodPath as v,isContextFunction as M}from"../declaration-ingress.js";import{isFeatureSection as A,isApi as N,localValue as m}from"../model-bindings.js";import{isResourceValue as T,isResourceHook as x,isLoadingActivity as S,isSurfaceValue as P,isCommandOutcome as V}from"../retired-vocabulary.js";import{createRule as _}from"../rule.js";const d=new Map([["lane","commandLane"],["once","commandOnce"],["policy","commandPolicy"],["queueBy","commandQueueBy"]]),c=new Map([["concurrency",new Map([["latest","commandLatest"]])]]),W=new Map([["onDispose","effectOnDispose"],["skipInitial","effectSkipInitial"],["when","effectWhen"]]),B=new Map([["backpressure","eventBackpressure"]]),L=new Map([["key","deliveryKey"],["kind","deliveryKind"]]),j=new Map,g=["backpressure","delivery"],F=new Map([["overflow",new Map([["reject","deliveryReject"]])]]),p=new Map([["when","contributionWhen"]]),q=new Map([["pipe",{at:[2],carries:[],words:p}],["register",{at:[2],carries:[],words:p}],["slot",{at:[2],carries:[],words:p}]]),K=new Map([["from","conditionFrom"]]),U=new Map([["call",{at:[1,2],carries:[],values:c,words:d}],["calls",{at:[2],carries:[],values:c,words:d}],["command",{at:[1,2],carries:[],values:c,words:d}],["effect",{at:[2],carries:[],words:W}],["event",{at:[3],carries:g,words:B}],["resource.live",{at:[0],carriedValues:F,carries:g,words:j}],["select",{at:[2],carries:[],values:c,words:d}]]),Y=new Map([["activity","loadingActivity"],["invalidate","resourceInvalidate"],["policy","navigatorPolicy"],["status","commandOutcomeStatus"]]),z=new Map([["call","contextCall"],["calls","contextCalls"],["scope.keyed","scopeKeyed"]]),H=new Map([["ApplicationConditionBinding",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["ApplicationExecution",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["ApplicationImportBinding",{messageId:"runtimeInternalEntry",module:"@opetope/runtime/internal",replacement:"@opetope/runtime"}],["Call",{messageId:"commandName",replacement:"Command"}],["CallError",{messageId:"commandName",replacement:"CommandError"}],["CallInputArgs",{messageId:"commandName",replacement:"CommandInputArgs"}],["CallTimeoutError",{messageId:"commandName",replacement:"CommandTimeoutError"}],["NavigatorPolicy",{messageId:"navigatorPolicy",replacement:"NavigatorCatalogue"}],["ResourceActivity",{messageId:"devtoolsResourceActivity",module:"@opetope/devtools",replacement:"ResourceNodeActivity"}],["ResourceDisposer",{messageId:"resourceDisposer",replacement:"Disposer"}],["RunCallOptions",{messageId:"commandName",replacement:"RunCommandOptions"}],["externalReadable",{messageId:"fromExternal",replacement:"fromExternal"}],["runCall",{messageId:"commandName",replacement:"runCommand"}],["useCommands",{messageId:"useCommands",replacement:"useModel"}]]);function Q(a){return a.type===s.MemberExpression?a.property:a}function G(a){return a.imported.type===s.Identifier?a.imported.name:a.imported.value}const J=_({create(a){const r=a.sourceCode,b=e=>{const t=v(r,e.callee);if(t===void 0)return;const n={modifiers:U.get(t.method),word:z.get(t.method)};if(!(n.modifiers===void 0&&n.word===void 0))return M(r,t.fn)?n:void 0},h=(e,t)=>{const n=e.arguments[t];if(n===void 0||n.type===s.SpreadElement)return;const o=m(r,n);return o.type===s.ObjectExpression?o:void 0},D=e=>{const t=v(r,e.callee),n=t===void 0?void 0:q.get(t.method);if(!(t===void 0||n===void 0))return A(r,t.fn,"provides",!0)?n:void 0},I=e=>{if(N(r,e.callee,"@opetope/runtime","defineCondition",!0))return h(e,1)},l=(e,t)=>{for(const n of e.properties){if(n.type!==s.Property)continue;const o=u(n),i=o===void 0?void 0:t.get(o);i!==void 0&&a.report({messageId:i,node:n.key})}},C=(e,t)=>{const n=u(e),o=n===void 0?void 0:t.get(n);if(o===void 0)return;const i=m(r,e.value);return i.type===s.Literal&&typeof i.value=="string"?o.get(i.value):void 0},f=(e,t)=>{for(const n of e.properties){if(n.type!==s.Property)continue;const o=C(n,t);o!==void 0&&a.report({messageId:o,node:n.value})}},R=(e,t,n)=>{const o=O(e,t),i=o===void 0?void 0:m(r,o.value);i?.type===s.ObjectExpression&&(l(i,L),n.carriedValues!==void 0&&f(i,n.carriedValues))},w=(e,t)=>{for(const n of t.at){const o=h(e,n);if(o!==void 0){l(o,t.words),t.values!==void 0&&f(o,t.values);for(const i of t.carries)R(o,i,t)}}},y=e=>T(r,e)||x(r,e),k=(e,t)=>t==="resourceInvalidate"?y(e.object):t==="loadingActivity"?S(r,e):t==="navigatorPolicy"?P(r,e.object):V(r,e.object),E=(e,t)=>{const n=Y.get(t);return n!==void 0&&k(e,n)?n:void 0};return{CallExpression(e){const t=D(e);t!==void 0&&w(e,t);const n=I(e);n!==void 0&&l(n,K);const o=b(e);o!==void 0&&(o.word!==void 0&&a.report({messageId:o.word,node:Q(e.callee)}),o.modifiers!==void 0&&w(e,o.modifiers))},ImportDeclaration(e){if(e.source.value.startsWith("@opetope/"))for(const t of e.specifiers){if(t.type!==s.ImportSpecifier)continue;const n=G(t),o=H.get(n);o!==void 0&&(o.module===void 0||o.module===e.source.value)&&a.report({data:{replacement:o.replacement,word:n},messageId:o.messageId,node:t.imported})}},MemberExpression(e){if(e.computed||e.property.type!==s.Identifier)return;const t=E(e,e.property.name);t!==void 0&&a.report({messageId:t,node:e.property})},Property(e){if(u(e)!=="invalidate"||e.parent.type!==s.ObjectPattern)return;const t=e.parent.parent;t.type!==s.VariableDeclarator||t.init===null||y(t.init)&&a.report({messageId:"resourceInvalidate",node:e.key})}}},meta:{docs:{description:"A word the D365\u2013D411 wave or a decision after it retired is named with its replacement, never rewritten."},messages:{commandLane:'`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: "latest" }` where `policy: "latest"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',commandLatest:'`concurrency: "latest"` is now `concurrency: { pending: "latest" }`, and `{ lane, pending: "latest" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',commandName:"`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).",commandOnce:"`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).",commandOutcomeStatus:'`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged \u2014 `{ kind: "ok", value }`, `{ kind: "failed", error }`, `{ kind: "cancelled", reason }` (D411).',commandPolicy:'`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: "latest" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value \u2014 `{ by }`, `{ by, lane }` or `{ lane, pending: "latest" }` \u2014 so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',commandQueueBy:"`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).",conditionFrom:"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads \u2014 `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).",contextCall:"`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` \u2014 and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => \u2026`), a constant names it (`const refill: Command<Refill, void> = ctx.command(\u2026)`), and the annotation of that context \u2014 `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature \u2014 is what is left; a feature has the last two only. `command<Deposit>(\u2026)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).",contextCalls:'`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, "name")` answers one command and `select(source, ["a", "b"])` a record of them (D388).',contributionWhen:"`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the other mechanism \u2014 the declared `Condition`s of `defineFeature({ when })`, which the application resolves before anything exists. `scope.while` keeps its own `when` (D378, D411).",deliveryKey:'Inside a delivery record `key` is now `by`: `{ pending: "latest", by }` keeps one waiting place per key instead of one for the whole node (D374).',deliveryKind:'Inside a delivery record `kind` is now `pending`: `kind: "latest"` is `pending: "latest"`, and `kind: "queue"` is `pending: "all"` \u2014 the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',deliveryReject:'`overflow: "reject"` is now `overflow: "drop"`: one word at both nodes for \xABthe new one does not enter and `emit` answers `false`\xBB. `"flush-oldest"` is unchanged and still the default of a keyed delivery (D369, D411).',devtoolsResourceActivity:"`ResourceActivity` of `@opetope/devtools` is now `ResourceNodeActivity`: `@opetope/core` exports a `ResourceActivity` of its own \u2014 the union a `ready` state carries \u2014 and one name carried two unrelated types on two public entries. Import the core one under its own name (D411).",effectOnDispose:"`onDispose` is now `finalize`: an effect has two releases \u2014 the disposer a run returns, and the one the node itself gets once \u2014 and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).",effectSkipInitial:"`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite \u2014 which is why this rule names it and leaves the rewrite to you (D379).",effectWhen:"`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => \u2026`; a predicate whose whole job is to refuse the first run \u2014 `(_current, previous) => previous !== undefined` \u2014 is `initial: false`. This rule does not guess between them. The word `when` is untouched at a feature and at `scope.while`; at a contribution it is now `enabled` (D379, D411).",eventBackpressure:'`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: "latest"` for one waiting place that the newest payload takes, or the record \u2014 `{ pending, by }`, `{ pending, capacity, overflow }` \u2014 for the rest (D374).',fromExternal:"`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).",loadingActivity:"A `loading` state carries `failure`, not `activity`: read `state.failure` \u2014 `undefined` is \xABboth channels are still viable\xBB, and otherwise `channel` names the one that died terminally and `error` carries its failure. A `ready` state still carries `activity` whole, so this is one branch losing the field and not the field being renamed (D375).",navigatorPolicy:"`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data \u2014 a word when it has no parameters, a record when it has them (D411).",resourceDisposer:"`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell \u2014 `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).",resourceInvalidate:"`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means \xABmark stale, keep the data\xBB, so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).",runtimeInternalEntry:"`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).",scopeKeyed:"`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required \u2014 so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).",useCommands:"`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388)."},schema:[],type:"problem"},name:"no-retired-vocabulary"});export{J as noRetiredVocabulary};
|
|
2
|
+
//# sourceMappingURL=no-retired-vocabulary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-retired-vocabulary.js","sources":["../../src/rules/no-retired-vocabulary.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { findProperty, propertyName } from '../ast';\nimport { isContextFunction, methodPath } from '../declaration-ingress';\nimport { isApi, isFeatureSection, localValue } from '../model-bindings';\nimport {\n isCommandOutcome,\n isLoadingActivity,\n isResourceHook,\n isResourceValue,\n isSurfaceValue,\n} from '../retired-vocabulary';\nimport { createRule } from '../rule';\n\ntype MessageIds =\n | 'commandLane'\n | 'commandLatest'\n | 'commandName'\n | 'commandOnce'\n | 'commandOutcomeStatus'\n | 'commandPolicy'\n | 'commandQueueBy'\n | 'conditionFrom'\n | 'contextCall'\n | 'contextCalls'\n | 'contributionWhen'\n | 'deliveryKey'\n | 'deliveryKind'\n | 'deliveryReject'\n | 'devtoolsResourceActivity'\n | 'effectOnDispose'\n | 'effectSkipInitial'\n | 'effectWhen'\n | 'eventBackpressure'\n | 'fromExternal'\n | 'loadingActivity'\n | 'navigatorPolicy'\n | 'resourceDisposer'\n | 'resourceInvalidate'\n | 'runtimeInternalEntry'\n | 'scopeKeyed'\n | 'useCommands';\n\n/**\n * One name a package no longer exports, and the word that carries its meaning now. D411: `module` is written where\n * the name is retired at one entry and live at another — `ResourceActivity` is still what `@opetope/core` calls the\n * background work of a `ready` state, and `ApplicationConditionBinding` is still the name, on the entry an author\n * is meant to take it from.\n */\ntype Retired = { readonly messageId: MessageIds; readonly module?: string; readonly replacement: string };\n/**\n * Where one node keeps its modifiers, and which retired words live there. `at` holds every position the record is\n * written in across the two surfaces — `ctx.command(run, options)` and `own.command(within, run, options)` — and\n * nothing else at those positions is an object literal, so one list answers both. `carries` names the members whose\n * own record is read in turn: a delivery policy is a record inside a record.\n */\ntype Modifiers = {\n readonly at: readonly number[];\n readonly carries: readonly string[];\n /** D411: a word of a carried record whose **value** was retired, not its key: `overflow: 'reject'` is one. */\n readonly carriedValues?: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>;\n /** D459: the same fact one record higher — a word retired as the **value** of a field of this record itself. */\n readonly values?: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>;\n readonly words: ReadonlyMap<string, MessageIds>;\n};\n\n/** D387, D389: the four words the options record of a command declaration and of a selection retired. */\nconst COMMAND_WORDS = new Map<string, MessageIds>([\n ['lane', 'commandLane'],\n ['once', 'commandOnce'],\n ['policy', 'commandPolicy'],\n ['queueBy', 'commandQueueBy'],\n]);\n/** D459: the one value of that record that moved — the waiting place is written as the record that names it. */\nconst COMMAND_VALUES = new Map<string, ReadonlyMap<string, MessageIds>>([\n ['concurrency', new Map<string, MessageIds>([['latest', 'commandLatest']])],\n]);\n/** D379: the three modifiers of an effect run. */\nconst EFFECT_WORDS = new Map<string, MessageIds>([\n ['onDispose', 'effectOnDispose'],\n ['skipInitial', 'effectSkipInitial'],\n ['when', 'effectWhen'],\n]);\nconst EVENT_WORDS = new Map<string, MessageIds>([['backpressure', 'eventBackpressure']]);\n/** D374: the two fields inside the record, whichever of the two words the record itself is still written under. */\nconst DELIVERY_WORDS = new Map<string, MessageIds>([\n ['key', 'deliveryKey'],\n ['kind', 'deliveryKind'],\n]);\nconst NO_WORDS = new Map<string, MessageIds>();\nconst DELIVERY = ['backpressure', 'delivery'];\n/** D411: the one field of a delivery whose value moved: a full keyed capacity answers `drop`, not `reject`. */\nconst LIVE_DELIVERY_VALUES = new Map<string, ReadonlyMap<string, MessageIds>>([\n ['overflow', new Map<string, MessageIds>([['reject', 'deliveryReject']])],\n]);\n/** D411: the words a contribution writes beside its value, on the `provides` section that declares it. */\nconst CONTRIBUTION_WORDS = new Map<string, MessageIds>([['when', 'contributionWhen']]);\nconst CONTRIBUTION_RECORDS = new Map<string, Modifiers>([\n ['pipe', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n ['register', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n ['slot', { at: [2], carries: [], words: CONTRIBUTION_WORDS }],\n]);\n/** D411: the record `defineCondition` takes second; the word it named its source by is the one that moved. */\nconst CONDITION_WORDS = new Map<string, MessageIds>([['from', 'conditionFrom']]);\n\nconst OPTION_RECORDS = new Map<string, Modifiers>([\n ['call', { at: [1, 2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['calls', { at: [2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['command', { at: [1, 2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n ['effect', { at: [2], carries: [], words: EFFECT_WORDS }],\n ['event', { at: [3], carries: DELIVERY, words: EVENT_WORDS }],\n ['resource.live', { at: [0], carriedValues: LIVE_DELIVERY_VALUES, carries: DELIVERY, words: NO_WORDS }],\n ['select', { at: [2], carries: [], values: COMMAND_VALUES, words: COMMAND_WORDS }],\n]);\n\n/** D375, D411, D415: the members whose name is retired, each with the object it has to have been read on. */\nconst RETIRED_MEMBERS = new Map<string, MessageIds>([\n ['activity', 'loadingActivity'],\n ['invalidate', 'resourceInvalidate'],\n ['policy', 'navigatorPolicy'],\n ['status', 'commandOutcomeStatus'],\n]);\n\n/** D378, D385, D388: the methods themselves, named on a declaring context of this library. */\nconst RETIRED_METHODS = new Map<string, MessageIds>([\n ['call', 'contextCall'],\n ['calls', 'contextCalls'],\n ['scope.keyed', 'scopeKeyed'],\n]);\n\n/** D380, D385, D388: the names an `@opetope/*` entry no longer exports. The module itself is the proof. */\nconst RETIRED_IMPORTS = new Map<string, Retired>([\n [\n 'ApplicationConditionBinding',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n /** D411, D415: the entry retired three copies, and a table that held one left two migrations unnamed. */\n [\n 'ApplicationExecution',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n [\n 'ApplicationImportBinding',\n { messageId: 'runtimeInternalEntry', module: '@opetope/runtime/internal', replacement: '@opetope/runtime' },\n ],\n ['Call', { messageId: 'commandName', replacement: 'Command' }],\n ['CallError', { messageId: 'commandName', replacement: 'CommandError' }],\n ['CallInputArgs', { messageId: 'commandName', replacement: 'CommandInputArgs' }],\n ['CallTimeoutError', { messageId: 'commandName', replacement: 'CommandTimeoutError' }],\n ['NavigatorPolicy', { messageId: 'navigatorPolicy', replacement: 'NavigatorCatalogue' }],\n [\n 'ResourceActivity',\n { messageId: 'devtoolsResourceActivity', module: '@opetope/devtools', replacement: 'ResourceNodeActivity' },\n ],\n ['ResourceDisposer', { messageId: 'resourceDisposer', replacement: 'Disposer' }],\n ['RunCallOptions', { messageId: 'commandName', replacement: 'RunCommandOptions' }],\n ['externalReadable', { messageId: 'fromExternal', replacement: 'fromExternal' }],\n ['runCall', { messageId: 'commandName', replacement: 'runCommand' }],\n ['useCommands', { messageId: 'useCommands', replacement: 'useModel' }],\n]);\n\n/** What one call of a declaring context is, as far as this rule reads it: a retired word, retired modifiers, both. */\ntype Declared = { readonly modifiers: Modifiers | undefined; readonly word: MessageIds | undefined };\n\n/** The word a callee ends in: `keyed` of `own.scope.keyed`, or the plain name a destructured member is called by. */\nfunction calleeWord(callee: TSESTree.Node): TSESTree.Node {\n return callee.type === AST_NODE_TYPES.MemberExpression ? callee.property : callee;\n}\n\n/** The name an import carries in the module that exports it, past the local alias this file gave it. */\nfunction importedName(specifier: TSESTree.ImportSpecifier): string {\n return specifier.imported.type === AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;\n}\n\nconst noRetiredVocabulary = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n\n /**\n * The shape decides first and the context second: a call whose method this rule has nothing to say about never\n * pays for resolving the parameter it was written on (D309, D323).\n */\n const declaredCall = (node: TSESTree.CallExpression): Declared | undefined => {\n const named = methodPath(source, node.callee);\n\n if (named === undefined) return undefined;\n\n const declared: Declared = {\n modifiers: OPTION_RECORDS.get(named.method),\n word: RETIRED_METHODS.get(named.method),\n };\n\n if (declared.modifiers === undefined && declared.word === undefined) return undefined;\n\n return isContextFunction(source, named.fn) ? declared : undefined;\n };\n /** The record written at one of the positions this node takes its modifiers in, past the name it was given. */\n const recordAt = (node: TSESTree.CallExpression, position: number): TSESTree.ObjectExpression | undefined => {\n const argument = node.arguments[position];\n\n if (argument === undefined || argument.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n const value = localValue(source, argument);\n\n return value.type === AST_NODE_TYPES.ObjectExpression ? value : undefined;\n };\n /**\n * D411: a contribution is declared on `provides`, not on `own`, so the section that proves it is its own. The\n * shape decides first here too: nothing resolves a section for a method this rule has nothing to say about.\n */\n const contributionCall = (node: TSESTree.CallExpression): Modifiers | undefined => {\n const named = methodPath(source, node.callee);\n const modifiers = named === undefined ? undefined : CONTRIBUTION_RECORDS.get(named.method);\n\n if (named === undefined || modifiers === undefined) return undefined;\n\n return isFeatureSection(source, named.fn, 'provides', true) ? modifiers : undefined;\n };\n /** D411: `defineCondition(id, options)` is a name this package exports, so the import is what proves it. */\n const conditionRecord = (node: TSESTree.CallExpression): TSESTree.ObjectExpression | undefined => {\n if (!isApi(source, node.callee, '@opetope/runtime', 'defineCondition', true)) return undefined;\n\n return recordAt(node, 1);\n };\n const reportRecord = (record: TSESTree.ObjectExpression, words: ReadonlyMap<string, MessageIds>): void => {\n for (const property of record.properties) {\n if (property.type !== AST_NODE_TYPES.Property) continue;\n\n const name = propertyName(property);\n const messageId = name === undefined ? undefined : words.get(name);\n\n if (messageId !== undefined) context.report({ messageId, node: property.key });\n }\n };\n /** D411: the word this property was written with, where that word is a retired value of a known field. */\n const retiredValue = (\n property: TSESTree.Property,\n values: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>,\n ): MessageIds | undefined => {\n const name = propertyName(property);\n const written = name === undefined ? undefined : values.get(name);\n\n if (written === undefined) return undefined;\n\n const value = localValue(source, property.value);\n\n return value.type === AST_NODE_TYPES.Literal && typeof value.value === 'string'\n ? written.get(value.value)\n : undefined;\n };\n /** D411: the value a field of a carried record was written with, where that value is the retired word. */\n const reportValues = (\n record: TSESTree.ObjectExpression,\n values: ReadonlyMap<string, ReadonlyMap<string, MessageIds>>,\n ): void => {\n for (const property of record.properties) {\n if (property.type !== AST_NODE_TYPES.Property) continue;\n\n const messageId = retiredValue(property, values);\n\n if (messageId !== undefined) context.report({ messageId, node: property.value });\n }\n };\n const reportCarried = (record: TSESTree.ObjectExpression, carrier: string, modifiers: Modifiers): void => {\n const property = findProperty(record, carrier);\n const carried = property === undefined ? undefined : localValue(source, property.value);\n\n if (carried?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n reportRecord(carried, DELIVERY_WORDS);\n\n if (modifiers.carriedValues !== undefined) reportValues(carried, modifiers.carriedValues);\n };\n const reportModifiers = (node: TSESTree.CallExpression, modifiers: Modifiers): void => {\n for (const position of modifiers.at) {\n const record = recordAt(node, position);\n\n if (record === undefined) continue;\n\n reportRecord(record, modifiers.words);\n\n if (modifiers.values !== undefined) reportValues(record, modifiers.values);\n\n for (const carrier of modifiers.carries) reportCarried(record, carrier, modifiers);\n }\n };\n /** Both objects that used to answer `invalidate`: the Resource itself, and the record `useResource` builds. */\n const holdsVerbs = (node: TSESTree.Node): boolean => isResourceValue(source, node) || isResourceHook(source, node);\n /** The object each retired member has to be read on, asked only once the name has already matched. */\n const memberOwner = (node: TSESTree.MemberExpression, messageId: MessageIds): boolean => {\n if (messageId === 'resourceInvalidate') return holdsVerbs(node.object);\n\n if (messageId === 'loadingActivity') return isLoadingActivity(source, node);\n\n if (messageId === 'navigatorPolicy') return isSurfaceValue(source, node.object);\n\n return isCommandOutcome(source, node.object);\n };\n /**\n * The four members this rule reads off a value it can resolve (D415 added `surface.policy`, which the message\n * of `NavigatorPolicy` already named and the rule did not report). The name decides first and the resolution\n * second, so a member this rule has nothing to say about never pays for the object it was read on (D309, D323).\n */\n const retiredMember = (node: TSESTree.MemberExpression, name: string): MessageIds | undefined => {\n const messageId = RETIRED_MEMBERS.get(name);\n\n return messageId !== undefined && memberOwner(node, messageId) ? messageId : undefined;\n };\n\n return {\n CallExpression(node): void {\n const contribution = contributionCall(node);\n\n if (contribution !== undefined) reportModifiers(node, contribution);\n\n const condition = conditionRecord(node);\n\n if (condition !== undefined) reportRecord(condition, CONDITION_WORDS);\n\n const declared = declaredCall(node);\n\n if (declared === undefined) return;\n\n if (declared.word !== undefined) context.report({ messageId: declared.word, node: calleeWord(node.callee) });\n\n if (declared.modifiers !== undefined) reportModifiers(node, declared.modifiers);\n },\n ImportDeclaration(node): void {\n if (!node.source.value.startsWith('@opetope/')) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) continue;\n\n const word = importedName(specifier);\n const retired = RETIRED_IMPORTS.get(word);\n\n if (retired !== undefined && (retired.module === undefined || retired.module === node.source.value)) {\n context.report({\n data: { replacement: retired.replacement, word },\n messageId: retired.messageId,\n node: specifier.imported,\n });\n }\n }\n },\n MemberExpression(node): void {\n if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier) return;\n\n const messageId = retiredMember(node, node.property.name);\n\n if (messageId !== undefined) context.report({ messageId, node: node.property });\n },\n Property(node): void {\n if (propertyName(node) !== 'invalidate' || node.parent.type !== AST_NODE_TYPES.ObjectPattern) return;\n\n const declarator = node.parent.parent;\n\n if (declarator.type !== AST_NODE_TYPES.VariableDeclarator || declarator.init === null) return;\n\n if (holdsVerbs(declarator.init)) context.report({ messageId: 'resourceInvalidate', node: node.key });\n },\n };\n },\n meta: {\n docs: {\n description:\n 'A word the D365–D411 wave or a decision after it retired is named with its replacement, never rewritten.',\n },\n messages: {\n commandLane:\n '`lane` is now a member of `concurrency`: write `concurrency: lane`, `concurrency: { by, lane }` for a keyed queue, or `concurrency: { lane, pending: \"latest\" }` where `policy: \"latest\"` stood beside it. Which of the three this record meant is in what it wrote together, so nothing here is rewritten for you (D387).',\n commandLatest:\n '`concurrency: \"latest\"` is now `concurrency: { pending: \"latest\" }`, and `{ lane, pending: \"latest\" }` where the queue is shared. The word named the input that wins and never named what it wins, so the place is written out: one waiting slot at the end of the lane, taken by a newer input while the body in flight is left to finish. Nothing else about the policy moved (D459).',\n commandName:\n '`{{word}}` is now `{{replacement}}`: one word for a command, everywhere. The primitive was the last place the vocabulary still said `Call`, while the hook was already `useCommand` and the outcome `CommandOutcome`. There is no alias (D385).',\n commandOnce:\n '`once` is now `memoize`: it caches a successful result, it does not stop after one run. `addEventListener(type, handler, { once: true })` is what the platform means by the word, and it is not what this option ever did (D389).',\n commandOutcomeStatus:\n '`status` is now `kind` on a `CommandOutcome`: one word for the discriminant of a settled answer, the one `ResourceState`, `ResourceOperationOutcome`, `ResourceCommitOutcome`, `PaginationOutcome` and a screen result already carry. The three branches are unchanged — `{ kind: \"ok\", value }`, `{ kind: \"failed\", error }`, `{ kind: \"cancelled\", reason }` (D411).',\n commandPolicy:\n '`policy` is now `concurrency`: one word for the order the runs take, written as `queue`, `parallel`, a lane or `{ pending: \"latest\" }`. Where `queueBy` or `lane` stood beside it the three collapse into one value — `{ by }`, `{ by, lane }` or `{ lane, pending: \"latest\" }` — so the shape depends on which of them this record wrote, and that is why this rule names the word instead of rewriting it (D387, D459).',\n commandQueueBy:\n '`queueBy` is now a member of `concurrency`: write `concurrency: { by: input => key }`, or `concurrency: { by, lane }` where a lane stood beside it (D387).',\n conditionFrom:\n \"`from` of `defineCondition` is now `source`: write `defineCondition(id, { select, source })`. One word names the dependency a node reads — `resource.load({ source })` and `attach(source)` already say it, and this node's own refusals said `Condition source` about the field they called `from` (D410, D411).\",\n contextCall:\n '`call` is now `command`: `ctx.command(run, options?)` in a model, `own.command(within, run, options?)` in a feature. The body takes one argument, its context, and reads the declared input off it as `input` — and `Input` is inferred from every position of the call except the body, so it is written in the cheapest that fits: the result type of a model factory names it in the literal it returns, an annotated option names it (`dedupe: (deposit: Deposit) => …`), a constant names it (`const refill: Command<Refill, void> = ctx.command(…)`), and the annotation of that context — `({ input, update }: ModelCommandContext<Deposit>)` in a model, `({ input, source }: FeatureCommandContext<typeof within, Deposit>)` in a feature — is what is left; a feature has the last two only. `command<Deposit>(…)` is not a position: it pins `Output` to its default (D385, D386, D444, D455).',\n contextCalls:\n '`ctx.calls` and `own.calls` are now `ctx.select` and `own.select`: declaring a body and selecting a command a dependency already declares are two mechanisms, so they are two words. `select(source, \"name\")` answers one command and `select(source, [\"a\", \"b\"])` a record of them (D388).',\n contributionWhen:\n '`when` of a contribution is now `enabled`: write `slot(target, value, { enabled })`, and the same at `pipe` and `register`. A truth the runtime reads while the node exists is `enabled`, at a contribution and at a Resource alike; `when` names the other mechanism — the declared `Condition`s of `defineFeature({ when })`, which the application resolves before anything exists. `scope.while` keeps its own `when` (D378, D411).',\n deliveryKey:\n 'Inside a delivery record `key` is now `by`: `{ pending: \"latest\", by }` keeps one waiting place per key instead of one for the whole node (D374).',\n deliveryKind:\n 'Inside a delivery record `kind` is now `pending`: `kind: \"latest\"` is `pending: \"latest\"`, and `kind: \"queue\"` is `pending: \"all\"` — the backlog only an event has. The discriminant moved because with one dimension shared by both nodes, the discriminant is that dimension (D374).',\n deliveryReject:\n '`overflow: \"reject\"` is now `overflow: \"drop\"`: one word at both nodes for «the new one does not enter and `emit` answers `false`». `\"flush-oldest\"` is unchanged and still the default of a keyed delivery (D369, D411).',\n devtoolsResourceActivity:\n '`ResourceActivity` of `@opetope/devtools` is now `ResourceNodeActivity`: `@opetope/core` exports a `ResourceActivity` of its own — the union a `ready` state carries — and one name carried two unrelated types on two public entries. Import the core one under its own name (D411).',\n effectOnDispose:\n '`onDispose` is now `finalize`: an effect has two releases — the disposer a run returns, and the one the node itself gets once — and an `on*` prefix did not tell them apart. No word of the authoring vocabulary carries that prefix any more (D379).',\n effectSkipInitial:\n '`skipInitial` is now `initial`, and the polarity is inverted: `skipInitial: true` is `initial: false`, and `initial` defaults to `true`. Both words are booleans, so carrying the value across unchanged compiles and means the opposite — which is why this rule names it and leaves the rewrite to you (D379).',\n effectWhen:\n '`when` at an effect is now `filter` or `initial`, and which one depends on what the predicate asks. A predicate that selects values is `filter: (current, previous) => …`; a predicate whose whole job is to refuse the first run — `(_current, previous) => previous !== undefined` — is `initial: false`. This rule does not guess between them. The word `when` is untouched at a feature and at `scope.while`; at a contribution it is now `enabled` (D379, D411).',\n eventBackpressure:\n '`backpressure` is now `delivery`: one word at both nodes that admit a producer. Write `delivery: \"latest\"` for one waiting place that the newest payload takes, or the record — `{ pending, by }`, `{ pending, capacity, overflow }` — for the rest (D374).',\n fromExternal:\n '`externalReadable` is now `fromExternal`: `from*` names where a `Readable` comes from, beside `fromMaybe`. The old name was built from its result, which every word of that module already answers (D380).',\n loadingActivity:\n 'A `loading` state carries `failure`, not `activity`: read `state.failure` — `undefined` is «both channels are still viable», and otherwise `channel` names the one that died terminally and `error` carries its failure. A `ready` state still carries `activity` whole, so this is one branch losing the field and not the field being renamed (D375).',\n navigatorPolicy:\n '`NavigatorPolicy` is now `NavigatorCatalogue`, and `surface.policy` is now `surface.catalogue`: this is four callbacks a navigator asks, and D291 made a policy data — a word when it has no parameters, a record when it has them (D411).',\n resourceDisposer:\n '`ResourceDisposer` is now `Disposer`: one name for `() => Awaitable<void>`, which three of them used to spell — `Disposer`, `ResourceDisposer` and `FeatureDisposer`. It travels on `@opetope/core/internal` beside `Awaitable` (D411).',\n resourceInvalidate:\n '`invalidate()` is now `reset()`: it clears the whole cache, drops the current data, opens a new epoch and restarts every configured channel. Everywhere else `invalidate` means «mark stale, keep the data», so the old name promised the opposite of what the verb does; it now names no operation of a Resource at all (D373).',\n runtimeInternalEntry:\n '`{{word}}` is published by `{{replacement}}`: import it from there. Three names were published by both entries and every consumer took the internal copy, so the copies are gone and the names are not (D411, D415).',\n scopeKeyed:\n '`scope.keyed(source, open, { key })` is now `scope.each(source, key, open)`: `each` says what the node does, one child per element, and the key is required — so it stands between the list it identifies and the child it opens. The trailing record is gone, which makes this a move and not only a rename (D378).',\n useCommands:\n '`useCommands` is gone: a record of command consumers comes from `useModel(Declaration, select)`, which has an owner and a lifetime. One command is still `useCommand` (D388).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-retired-vocabulary',\n});\n\nexport { noRetiredVocabulary };\n"],"names":["COMMAND_WORDS","COMMAND_VALUES","EFFECT_WORDS","EVENT_WORDS","DELIVERY_WORDS","NO_WORDS","DELIVERY","LIVE_DELIVERY_VALUES","CONTRIBUTION_WORDS","CONTRIBUTION_RECORDS","CONDITION_WORDS","OPTION_RECORDS","RETIRED_MEMBERS","RETIRED_METHODS","RETIRED_IMPORTS","calleeWord","callee","AST_NODE_TYPES","importedName","specifier","noRetiredVocabulary","createRule","context","source","declaredCall","node","named","methodPath","declared","isContextFunction","recordAt","position","argument","value","localValue","contributionCall","modifiers","isFeatureSection","conditionRecord","isApi","reportRecord","record","words","property","name","propertyName","messageId","retiredValue","values","written","reportValues","reportCarried","carrier","findProperty","carried","reportModifiers","holdsVerbs","isResourceValue","isResourceHook","memberOwner","isLoadingActivity","isSurfaceValue","isCommandOutcome","retiredMember","contribution","condition","word","retired","declarator"],"mappings":"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,CACX,GAAIoB,EAAapB,CAAI,IAAM,cAAgBA,EAAK,OAAO,OAASR,EAAe,cAAe,OAE9F,MAAMmD,EAAa3C,EAAK,OAAO,OAE3B2C,EAAW,OAASnD,EAAe,oBAAsBmD,EAAW,OAAS,MAE7EZ,EAAWY,EAAW,IAAI,GAAG9C,EAAQ,OAAO,CAAE,UAAW,qBAAsB,KAAMG,EAAK,IAAK,CACrG,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YACE,+GACH,EACD,SAAU,CACR,YACE,6TACF,cACE,0XACF,YACE,kPACF,YACE,oOACF,qBACE,8WACF,cACE,saACF,eACE,6JACF,cACE,yTACF,YACE,04BACF,aACE,8RACF,iBACE,+aACF,YACE,oJACF,aACE,8RACF,eACE,kOACF,yBACE,kSACF,gBACE,kQACF,kBACE,wTACF,WACE,wdACF,kBACE,wQACF,aACE,6MACF,gBACE,qWACF,gBACE,kPACF,iBACE,+OACF,mBACE,yUACF,qBACE,uNACF,WACE,4TACF,YACE,+KACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,uBACP,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type MessageIds = 'writeAfterInvokedSourceWrite' | 'writeAfterSourceWrite';
|
|
3
|
+
declare const noWriteAfterSourceWrite: TSESLint.RuleModule<MessageIds, [], unknown, TSESLint.RuleListener> & {
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
export { noWriteAfterSourceWrite };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AST_NODE_TYPES as a}from"@typescript-eslint/utils";import{within as E}from"../ast.js";import{contextMembers as C,fromContext as k}from"../context-members.js";import{methodPath as h,isContextFunction as x,isModelContextFunction as A,commandBody as D}from"../declaration-ingress.js";import{declaredEffect as T}from"../effect-declarations.js";import{localValue as u,unwrap as f,variableOf as d,isApi as O,enclosingFunction as W,isFunction as M}from"../model-bindings.js";import{createRule as N}from"../rule.js";const m=["update"],P=["invoke"],w=1,R=0,B=new Set(["!=","!==","<","<=","==","===",">",">=","in","instanceof"]),F=new Set(["!","typeof"]),g=new Set([a.ReturnStatement,a.ThrowStatement]);function _(t,e){const n=u(t,e);if(n.type!==a.CallExpression)return!1;const r=h(t,n.callee);return r?.method==="state"&&x(t,r.fn)}function q(t,e){if(e.type!==a.CallExpression||!O(t,e.callee,"@opetope/core","derive",!0))return;const[n,r]=e.arguments;if(n===void 0)return;const i=f(n);return i.type!==a.ArrayExpression?{dependencies:[i],selector:r}:{dependencies:i.elements.flatMap(o=>o===null?[]:[o]),selector:r}}function K(t){const{parent:e}=t;return e.type===a.BinaryExpression?B.has(e.operator):e.type===a.UnaryExpression?F.has(e.operator):(e.type===a.ConditionalExpression||e.type===a.IfStatement)&&e.test===t}function L(t,e,n){const r=e===void 0?void 0:u(t,e);if(r===void 0||!M(r))return;const i=r.params[n];return i?.type===a.Identifier?i:void 0}function V(t,e,n){const r=L(t,e,n);return(r===void 0?null:d(t,r))?.references.some(({identifier:o})=>o.type===a.Identifier&&!K(o))??!1}function S(t,e,n){const r=f(n);if(r.type!==a.Identifier)return;const i=d(t,r);i!==null&&_(t,r)&&e.add(i)}function Y(t,e){const n=new Set;S(t,n,e);const r=q(t,u(t,e));if(r===void 0)return n;for(const[i,o]of r.dependencies.entries())V(t,r.selector,i)&&S(t,n,o);return n}function p(t,e,n,r,i){const o=C(t,e,r,[i],n.declarators);return n.calls.filter(s=>E(s,e)&&W(t,s)===e).filter(s=>k(t,s.callee,o,i)).sort((s,c)=>s.range[0]-c.range[0])}function X(t,e,n){return p(t,e,n,w,m[0])}function v(t,e,n){const[r]=e.arguments;if(r===void 0)return!1;const i=f(r);if(i.type!==a.Identifier)return!1;const o=d(t,i);return o!==null&&n.has(o)}function z(t,e){const n=u(t,f(e));if(n.type!==a.CallExpression)return;const r=h(t,n.callee);if(r?.method==="command")return A(t,r.fn)?D(t,n):void 0}function U(t,e,n,r){const[i]=e.arguments,o=i===void 0?void 0:z(t,i);return o===void 0?!1:p(t,o,n,R,m[0]).some(s=>v(t,s,r))}function y(t){let e=t;for(;e.type===a.BlockStatement;){const n=e.body.at(-1);if(n===void 0)return;e=n}return e}function b(t){const e=y(t);return e!==void 0&&g.has(e.type)}function j(t){const e=y(t);return e===void 0?!1:g.has(e.type)?!0:e.type!==a.IfStatement||e.alternate===null?!1:b(e.consequent)&&b(e.alternate)}function G(t,e,n){const r=e.range[1],i=n.range[0],o=s=>s.range[0]>=r&&s.range[1]<=i&&j(s);return t.getAncestors(e).some(s=>s.type===a.BlockStatement&&s.body.some(o))}function H(t,e){const n=Math.min(t.length,e.length);let r=0;for(;r<n&&t[r]===e[r];)r+=1;return r}function I(t,e,n){const r=[...t.getAncestors(e),e],i=[...t.getAncestors(n),n],o=H(r,i),s=o===0?void 0:r[o-1],c=r[o],l=i[o];return s?.type!==a.BlockStatement||c===void 0||l===void 0?!1:c.range[1]<=l.range[0]&&!G(t,e,n)}const J=N({create(t){const e=t.sourceCode,n={calls:[],declarators:[]},r=(i,o)=>{const s=X(e,i,n);for(const c of s)v(e,c,o)&&s.some(l=>I(e,c,l))&&t.report({messageId:"writeAfterSourceWrite",node:c});for(const c of p(e,i,n,w,P[0]))U(e,c,n,o)&&s.some(l=>I(e,c,l))&&t.report({messageId:"writeAfterInvokedSourceWrite",node:c})};return{CallExpression(i){n.calls.push(i)},"Program:exit"(){for(const i of n.calls){const o=T(e,i);if(o===void 0)continue;const s=Y(e,o.from);s.size>0&&r(o.run,s)}},VariableDeclarator(i){n.declarators.push(i)}}},meta:{docs:{description:"A write into the own source of an effect, its own or a command it invokes, is that run last."},messages:{writeAfterInvokedSourceWrite:"The command this call starts writes into a cell this run's own source reads, so unless that write leaves the published value equal it ends this run inside the command: `invoke` gives the command the cancellation of the run, and one abort ends them both. Everything of the run after this call is then lost \u2014 a write it reaches is dropped and reaches the reporter as `LostWrite`, and a body still awaiting this call is cut short with a cancellation and reaches no write at all. Make the write into the source the last write of the run, or keep what the run accumulates out of its source and read it with `getSnapshot()`, or take `execution.capture(state)` before the moving write \u2014 a commit outlives the run and answers `applied` or `cancelled` (D288, D319, D365, D432, D447).",writeAfterSourceWrite:"This write goes into a cell this run's own source reads, so unless it leaves the published value equal it ends this run inside the write itself, and every write of the run after it is dropped. The first of those dropped writes reaches the reporter as `LostWrite`, once per run, saying the rest went with it. Make the write into the source the last write of the run, or keep what the run accumulates out of its source and read it with `getSnapshot()`, or take `execution.capture(state)` before the moving write \u2014 a commit outlives the run and answers `applied` or `cancelled` (D288, D319, D365, D432, D447)."},schema:[],type:"problem"},name:"no-write-after-source-write"});export{J as noWriteAfterSourceWrite};
|
|
2
|
+
//# sourceMappingURL=no-write-after-source-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-write-after-source-write.js","sources":["../../src/rules/no-write-after-source-write.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { within } from '../ast';\nimport { contextMembers, fromContext } from '../context-members';\nimport { commandBody, isContextFunction, isModelContextFunction, methodPath } from '../declaration-ingress';\nimport { declaredEffect } from '../effect-declarations';\nimport type { FunctionNode } from '../model-bindings';\nimport { enclosingFunction, isApi, isFunction, localValue, unwrap, variableOf } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'writeAfterInvokedSourceWrite' | 'writeAfterSourceWrite';\ntype Variable = TSESLint.Scope.Variable;\n\n/** What one file wrote, collected before any write is judged: the rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly calls: TSESTree.CallExpression[];\n readonly declarators: TSESTree.VariableDeclarator[];\n};\n\n/** The dependencies of a `derive` and the selector that publishes a value from them. */\ntype Derived = {\n readonly dependencies: readonly TSESTree.Node[];\n readonly selector: TSESTree.CallExpressionArgument | undefined;\n};\n\n/** The one name this rule reads off the context of a run — its writer — and the place a run receives that context. */\nconst WRITER_KEYS = ['update'] as const;\n/** D447: the other name it reads off a run — the word a run starts an already declared command with. */\nconst INVOKE_KEYS = ['invoke'] as const;\n/** D379: position 1 — the value the run answers is its first parameter, and the context follows it. */\nconst CONTEXT_POSITION = 1;\n/** D386: a command body takes one argument, its context, so the same writer is read one position earlier. */\nconst COMMAND_POSITION = 0;\n\n/** A read of a cell that answers a question about it instead of publishing it. */\nconst COMPARISONS = new Set(['!=', '!==', '<', '<=', '==', '===', '>', '>=', 'in', 'instanceof']);\nconst CLASSIFIERS = new Set(['!', 'typeof']);\n\n/** The two statements that end a run for good. */\nconst EXITS = new Set<string>([AST_NODE_TYPES.ReturnStatement, AST_NODE_TYPES.ThrowStatement]);\n\n/** A cell of this model, written where the declaration can be read: `context.state(…)` on a declaring context. */\nfunction isDeclaredCell(source: TSESLint.SourceCode, node: TSESTree.Identifier): boolean {\n const value = localValue(source, node);\n\n if (value.type !== AST_NODE_TYPES.CallExpression) return false;\n\n const named = methodPath(source, value.callee);\n\n return named?.method === 'state' && isContextFunction(source, named.fn);\n}\n\n/** The dependencies of `derive([a, cell], …)` and of `derive(cell, …)` alike, as the declaration spells them. */\nfunction derivedFrom(source: TSESLint.SourceCode, node: TSESTree.Node): Derived | undefined {\n if (node.type !== AST_NODE_TYPES.CallExpression) return undefined;\n\n if (!isApi(source, node.callee, '@opetope/core', 'derive', true)) return undefined;\n\n const [dependencies, selector] = node.arguments;\n\n if (dependencies === undefined) return undefined;\n\n const listed = unwrap(dependencies);\n\n if (listed.type !== AST_NODE_TYPES.ArrayExpression) return { dependencies: [listed], selector };\n\n // A hole in the literal is not a dependency, and a spread is one this rule cannot read; both leave the list here.\n return { dependencies: listed.elements.flatMap(element => (element === null ? [] : [element])), selector };\n}\n\n/**\n * Whether this read carries the value of the cell into what the selector publishes. A read that only asks a question\n * about it — `current === null`, `!current`, `typeof current`, `current ? a : b` — classifies the cell rather than\n * publishing it, and a write that keeps the answer keeps the published value.\n */\nfunction classified(node: TSESTree.Identifier): boolean {\n const { parent } = node;\n\n if (parent.type === AST_NODE_TYPES.BinaryExpression) return COMPARISONS.has(parent.operator);\n\n if (parent.type === AST_NODE_TYPES.UnaryExpression) return CLASSIFIERS.has(parent.operator);\n\n if (parent.type === AST_NODE_TYPES.ConditionalExpression) return parent.test === node;\n\n return parent.type === AST_NODE_TYPES.IfStatement && parent.test === node;\n}\n\n/** The parameter of the selector that stands for this dependency, where the selector is one this file can read. */\nfunction selectorParameter(\n source: TSESLint.SourceCode,\n selector: TSESTree.Node | undefined,\n position: number,\n): TSESTree.Identifier | undefined {\n const fn = selector === undefined ? undefined : localValue(source, selector);\n\n if (fn === undefined || !isFunction(fn)) return undefined;\n\n const parameter = fn.params[position];\n\n return parameter?.type === AST_NODE_TYPES.Identifier ? parameter : undefined;\n}\n\n/**\n * Whether the value the source publishes travels with this dependency. The law has a carve-out — a write that leaves\n * the published value equal ends nothing (spec §2.11) — and a selector that only classifies its dependency is that\n * carve-out written down: a `derive` publishing `current === null ? 'none' : 'some'` does not move when one key\n * replaces another. So the selector has to be readable here and has to read this dependency as a value at least once;\n * a selector assembled elsewhere, one that drops the dependency, and one that only asks a question about it all leave\n * the rule silent.\n */\nfunction travels(source: TSESLint.SourceCode, selector: TSESTree.Node | undefined, position: number): boolean {\n const parameter = selectorParameter(source, selector, position);\n const variable = parameter === undefined ? null : variableOf(source, parameter);\n\n // A JSX tag name is not a read of this value, and neither is anything else this rule cannot place.\n return (\n variable?.references.some(\n ({ identifier }) => identifier.type === AST_NODE_TYPES.Identifier && !classified(identifier),\n ) ?? false\n );\n}\n\n/** This name, where it is a cell of this model, joins the set the source republishes. */\nfunction addCell(source: TSESLint.SourceCode, cells: Set<Variable>, node: TSESTree.Node): void {\n const named = unwrap(node);\n\n if (named.type !== AST_NODE_TYPES.Identifier) return;\n\n const variable = variableOf(source, named);\n\n if (variable !== null && isDeclaredCell(source, named)) cells.add(variable);\n}\n\n/**\n * The cells of this model whose value the effect's source republishes: the source itself where a cell is the source,\n * and every dependency of a `derive` this scope holds whose value the selector publishes. A source assembled in\n * another function, a cell that arrived as a parameter and a `derive` over a `derive` name nothing here — the rule\n * then stays silent, because it cannot prove a write moves the source.\n */\nfunction sourceCells(source: TSESLint.SourceCode, from: TSESTree.Node): ReadonlySet<Variable> {\n const cells = new Set<Variable>();\n\n // A cell as the source itself publishes its own value, so it travels by construction.\n addCell(source, cells, from);\n\n const derived = derivedFrom(source, localValue(source, from));\n\n if (derived === undefined) return cells;\n\n for (const [position, dependency] of derived.dependencies.entries()) {\n if (travels(source, derived.selector, position)) addCell(source, cells, dependency);\n }\n\n return cells;\n}\n\n/**\n * The calls one body makes through one name of its own context, in its own body and in the order they are written.\n * The position is part of the contract: a reaction run takes its context second and a command body first (D386).\n */\nfunction callsThrough<Key extends string>(\n source: TSESLint.SourceCode,\n body: FunctionNode,\n written: Written,\n position: number,\n key: Key,\n): readonly TSESTree.CallExpression[] {\n const members = contextMembers(source, body, position, [key], written.declarators);\n\n return written.calls\n .filter(call => within(call, body) && enclosingFunction(source, call) === body)\n .filter(call => fromContext(source, call.callee, members, key))\n .sort((left, right) => left.range[0] - right.range[0]);\n}\n\n/** The writes of one run through the writer it was given, in its own body and in the order they are written. */\nfunction writesOf(\n source: TSESLint.SourceCode,\n run: FunctionNode,\n written: Written,\n): readonly TSESTree.CallExpression[] {\n return callsThrough(source, run, written, CONTEXT_POSITION, WRITER_KEYS[0]);\n}\n\n/** `update(cell, …)` over a cell of this source: the readable a write names first, resolved to its declaration. */\nfunction writesSourceCell(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n cells: ReadonlySet<Variable>,\n): boolean {\n const [target] = call.arguments;\n\n if (target === undefined) return false;\n\n const named = unwrap(target);\n\n if (named.type !== AST_NODE_TYPES.Identifier) return false;\n\n const variable = variableOf(source, named);\n\n return variable !== null && cells.has(variable);\n}\n\n/**\n * D447: the body of the command this name holds, where it is declared on a model's own context in this module. The\n * run and the commands it invokes are one authority — `invoke` hands the command the cancellation of the run — so a\n * write of such a command into a cell the run's source reads ends the run exactly as the run's own write does.\n */\nfunction invokedCommand(source: TSESLint.SourceCode, node: TSESTree.Node): FunctionNode | undefined {\n const value = localValue(source, unwrap(node));\n\n if (value.type !== AST_NODE_TYPES.CallExpression) return undefined;\n\n const named = methodPath(source, value.callee);\n\n if (named?.method !== 'command') return undefined;\n\n return isModelContextFunction(source, named.fn) ? commandBody(source, value) : undefined;\n}\n\n/** `invoke(release)` where the body of `release` writes a cell of this source: the moving write is the command's. */\nfunction invokesSourceWrite(\n source: TSESLint.SourceCode,\n call: TSESTree.CallExpression,\n written: Written,\n cells: ReadonlySet<Variable>,\n): boolean {\n const [target] = call.arguments;\n const body = target === undefined ? undefined : invokedCommand(source, target);\n\n if (body === undefined) return false;\n\n return callsThrough(source, body, written, COMMAND_POSITION, WRITER_KEYS[0]).some(write =>\n writesSourceCell(source, write, cells),\n );\n}\n\n/** The statement a block ends on, looking inward: `{ … { return } }` ends on that `return`. */\nfunction lastStatement(statement: TSESTree.Statement): TSESTree.Statement | undefined {\n let current = statement;\n\n while (current.type === AST_NODE_TYPES.BlockStatement) {\n const last = current.body.at(-1);\n\n if (last === undefined) return undefined;\n\n current = last;\n }\n\n return current;\n}\n\n/** A branch that ends on a `return` or a `throw`. */\nfunction leavesDirectly(statement: TSESTree.Statement): boolean {\n const last = lastStatement(statement);\n\n return last !== undefined && EXITS.has(last.type);\n}\n\n/** A statement past which the run has left for good; a `break` or a `continue` leaves the code below it reachable. */\nfunction exits(statement: TSESTree.Statement): boolean {\n const last = lastStatement(statement);\n\n if (last === undefined) return false;\n\n if (EXITS.has(last.type)) return true;\n\n // `if (a) { return } else { throw }` leaves by either branch; an `if` nested deeper is not read.\n if (last.type !== AST_NODE_TYPES.IfStatement || last.alternate === null) return false;\n\n return leavesDirectly(last.consequent) && leavesDirectly(last.alternate);\n}\n\n/**\n * Whether the run leaves for good between the two writes. Only a block that holds the first write is read, and only\n * the statements of it that stand after that write and before the second one, so the `return` of\n * `if (key === null) { update(key, null); return; }` is found and the write below the `if` is left alone.\n */\nfunction leavesBetween(source: TSESLint.SourceCode, first: TSESTree.Node, second: TSESTree.Node): boolean {\n const from = first.range[1];\n const to = second.range[0];\n const between = (statement: TSESTree.Statement): boolean =>\n statement.range[0] >= from && statement.range[1] <= to && exits(statement);\n\n return source\n .getAncestors(first)\n .some(node => node.type === AST_NODE_TYPES.BlockStatement && node.body.some(between));\n}\n\n/** The step at which two paths from the same root part company. */\nfunction divergence(outer: readonly TSESTree.Node[], inner: readonly TSESTree.Node[]): number {\n const shared = Math.min(outer.length, inner.length);\n let at = 0;\n\n while (at < shared && outer[at] === inner[at]) at += 1;\n\n return at;\n}\n\n/**\n * Whether the second write can run after the first: the two stand in one block, in different statements, in this\n * order, with no unconditional exit of the run between them. Two writes of one statement — the branches of an `if`,\n * the arms of a ternary, the halves of a `try`, the sides of a comma — are not a sequence, and a rule that must not\n * cry wolf says nothing about them.\n */\nfunction follows(source: TSESLint.SourceCode, first: TSESTree.Node, second: TSESTree.Node): boolean {\n const outer = [...source.getAncestors(first), first];\n const inner = [...source.getAncestors(second), second];\n const at = divergence(outer, inner);\n const block = at === 0 ? undefined : outer[at - 1];\n const before = outer[at];\n const after = inner[at];\n\n if (block?.type !== AST_NODE_TYPES.BlockStatement || before === undefined || after === undefined) return false;\n\n return before.range[1] <= after.range[0] && !leavesBetween(source, first, second);\n}\n\nconst noWriteAfterSourceWrite = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const written: Written = { calls: [], declarators: [] };\n\n const reportRun = (run: FunctionNode, cells: ReadonlySet<Variable>): void => {\n const writes = writesOf(source, run, written);\n\n for (const write of writes) {\n if (!writesSourceCell(source, write, cells)) continue;\n\n if (writes.some(later => follows(source, write, later))) {\n context.report({ messageId: 'writeAfterSourceWrite', node: write });\n }\n }\n\n // D447: the same question of a call, because a command the run invokes writes under the authority of this run.\n for (const call of callsThrough(source, run, written, CONTEXT_POSITION, INVOKE_KEYS[0])) {\n if (!invokesSourceWrite(source, call, written, cells)) continue;\n\n if (writes.some(later => follows(source, call, later))) {\n context.report({ messageId: 'writeAfterInvokedSourceWrite', node: call });\n }\n }\n };\n\n return {\n CallExpression(node): void {\n written.calls.push(node);\n },\n 'Program:exit'(): void {\n for (const call of written.calls) {\n const declared = declaredEffect(source, call);\n\n if (declared === undefined) continue;\n\n const cells = sourceCells(source, declared.from);\n\n if (cells.size > 0) reportRun(declared.run, cells);\n }\n },\n VariableDeclarator(node): void {\n written.declarators.push(node);\n },\n };\n },\n meta: {\n docs: {\n description: 'A write into the own source of an effect, its own or a command it invokes, is that run last.',\n },\n messages: {\n /**\n * D447: the moving write is the command's, and the loss is the run's. This form is the one the runtime often\n * cannot report — the run is awaiting that call when its own command ends it, so the body never reaches a\n * write there is anything to drop — which is why the editor has to name it. The remedies are the same three,\n * in the same words, for the same reason as below.\n */\n writeAfterInvokedSourceWrite:\n \"The command this call starts writes into a cell this run's own source reads, so unless that write leaves the published value equal it ends this run inside the command: `invoke` gives the command the cancellation of the run, and one abort ends them both. Everything of the run after this call is then lost — a write it reaches is dropped and reaches the reporter as `LostWrite`, and a body still awaiting this call is cut short with a cancellation and reaches no write at all. Make the write into the source the last write of the run, or keep what the run accumulates out of its source and read it with `getSnapshot()`, or take `execution.capture(state)` before the moving write — a commit outlives the run and answers `applied` or `cancelled` (D288, D319, D365, D432, D447).\",\n /**\n * D442: the remedies are the three the runtime names in `LostWrite`, and the sentence below is that record's\n * own sentence word for word. The author reads this one first — the editor answers before the run does — so\n * the one remedy that saves the write whole, a `capture` taken before the moving write, cannot be the one\n * only the runtime names (D319, D432).\n */\n writeAfterSourceWrite:\n \"This write goes into a cell this run's own source reads, so unless it leaves the published value equal it ends this run inside the write itself, and every write of the run after it is dropped. The first of those dropped writes reaches the reporter as `LostWrite`, once per run, saying the rest went with it. Make the write into the source the last write of the run, or keep what the run accumulates out of its source and read it with `getSnapshot()`, or take `execution.capture(state)` before the moving write — a commit outlives the run and answers `applied` or `cancelled` (D288, D319, D365, D432, D447).\",\n },\n schema: [],\n type: 'problem',\n },\n name: 'no-write-after-source-write',\n});\n\nexport { noWriteAfterSourceWrite };\n"],"names":["WRITER_KEYS","INVOKE_KEYS","CONTEXT_POSITION","COMMAND_POSITION","COMPARISONS","CLASSIFIERS","EXITS","AST_NODE_TYPES","isDeclaredCell","source","node","value","localValue","named","methodPath","isContextFunction","derivedFrom","isApi","dependencies","selector","listed","unwrap","element","classified","parent","selectorParameter","position","fn","isFunction","parameter","travels","variableOf","identifier","addCell","cells","variable","sourceCells","from","derived","dependency","callsThrough","body","written","key","members","contextMembers","call","within","enclosingFunction","fromContext","left","right","writesOf","run","writesSourceCell","target","invokedCommand","isModelContextFunction","commandBody","invokesSourceWrite","write","lastStatement","statement","current","last","leavesDirectly","exits","leavesBetween","first","second","to","between","divergence","outer","inner","shared","at","follows","block","before","after","noWriteAfterSourceWrite","createRule","context","reportRun","writes","later","declared","declaredEffect"],"mappings":"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"}
|