@opetope/lint 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +464 -4
- package/README.md +314 -63
- package/README.ru.md +253 -63
- 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 -1
- 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-effect-current.js","sources":["../../src/rules/prefer-effect-current.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 { isContextFunction, methodPath } from '../declaration-ingress';\nimport type { FunctionNode } from '../model-bindings';\nimport { isFunction, unwrap, variableOf } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'preferEffectCurrent';\n\n/** What one file wrote, collected before any read is judged: the rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly barriers: TSESTree.Node[];\n readonly calls: TSESTree.CallExpression[];\n readonly functions: FunctionNode[];\n};\n\n/** One `effect(source, run, options?)` declared on a context of this library, with what a rewrite of it needs. */\ntype Declared = { readonly fixable: boolean; readonly from: TSESTree.Identifier; readonly run: FunctionNode };\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/** `s.getSnapshot()`, and the receiver it was read on; a reference that is not called reads nothing. */\nfunction snapshotReceiver(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const { callee } = node;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return undefined;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return undefined;\n\n return node.arguments.length === 0 ? unwrap(callee.object) : undefined;\n}\n\n/** The same source: a name is the binding it resolves to, and two unresolved names are the same spelling. */\nfunction sameSource(source: TSESLint.SourceCode, from: TSESTree.Identifier, candidate: TSESTree.Node): boolean {\n if (candidate.type !== AST_NODE_TYPES.Identifier) return false;\n\n const declared = variableOf(source, from);\n const read = variableOf(source, candidate);\n\n return declared === null && read === null ? from.name === candidate.name : declared === read;\n}\n\n/** The local name `current` was destructured under, where the run took it that way. */\nfunction destructuredCurrent(pattern: TSESTree.ObjectPattern): string | undefined {\n for (const property of pattern.properties) {\n if (property.type !== AST_NODE_TYPES.Property || propertyName(property) !== 'current') continue;\n\n return property.value.type === AST_NODE_TYPES.Identifier ? property.value.name : undefined;\n }\n\n return undefined;\n}\n\n/**\n * What the read is replaced with. A named context answers `execution.current`; a destructured one answers the local\n * name. A run that took neither has no word for the value, so the report carries no fix.\n */\nfunction currentExpression(run: FunctionNode): string | undefined {\n const parameter = run.params[0];\n\n if (parameter === undefined) return undefined;\n\n if (parameter.type === AST_NODE_TYPES.Identifier) return `${parameter.name}.current`;\n\n if (parameter.type !== AST_NODE_TYPES.ObjectPattern) return undefined;\n\n return destructuredCurrent(parameter);\n}\n\n/**\n * D296: `when` is a filter of values, not a gate on the lifetime of the effect, so under it the source moves without\n * restarting the run. `execution.current` is then the value this run was started for and the snapshot is the newer\n * one — two different facts, and a silent rewrite between them is exactly what a fixable rule may not do.\n */\nfunction declaresWhen(options: TSESTree.CallExpressionArgument | undefined): boolean {\n if (options === undefined) return false;\n\n const record = unwrap(options);\n\n // A record assembled elsewhere may carry the predicate, and a rule that rewrites code assumes it does.\n if (record.type !== AST_NODE_TYPES.ObjectExpression) return true;\n\n return findProperty(record, 'when') !== undefined;\n}\n\n/** The source and the body of an `effect`, where both are written in the shape this rule can answer for. */\nfunction effectArguments(call: TSESTree.CallExpression): Declared | undefined {\n const from = call.arguments[0];\n const run = call.arguments[1];\n\n if (from === undefined || from.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n if (run === undefined || !isFunction(run)) return undefined;\n\n return { fixable: !declaresWhen(call.arguments[2]), from, run };\n}\n\nconst preferEffectCurrent = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const written: Written = { barriers: [], calls: [], functions: [] };\n\n const effectRun = (call: TSESTree.CallExpression): Declared | undefined => {\n const named = methodPath(source, call.callee);\n\n if (named?.method !== 'effect') return undefined;\n\n const declared = effectArguments(call);\n\n if (declared === undefined) return undefined;\n\n return isContextFunction(source, named.fn) ? declared : undefined;\n };\n /** A function written inside the run runs later than the run does, and a snapshot read there is its own. */\n const nested = (node: TSESTree.Node, run: FunctionNode): boolean =>\n written.functions.some(fn => fn !== run && within(fn, run) && within(node, fn));\n /**\n * After an `await` the two are no longer the same value: `current` is what this run started with, the snapshot\n * is what the source holds now. The run usually restarts on a change, so they usually agree — and «usually» is\n * not a licence to rewrite, so past the first barrier the rule only says so.\n */\n const settled = (node: TSESTree.Node, run: FunctionNode): boolean =>\n written.barriers.some(barrier => within(barrier, run) && barrier.range[0] < node.range[0]);\n const report = (declared: Declared, read: TSESTree.CallExpression): void => {\n const replacement = currentExpression(declared.run);\n\n if (replacement === undefined || !declared.fixable || settled(read, declared.run)) {\n context.report({ messageId: 'preferEffectCurrent', node: read });\n\n return;\n }\n\n context.report({\n fix: fixer => fixer.replaceTextRange(read.range, replacement),\n messageId: 'preferEffectCurrent',\n node: read,\n });\n };\n const reportReads = (declared: Declared): void => {\n for (const read of written.calls) {\n if (!within(read, declared.run) || nested(read, declared.run)) continue;\n\n const receiver = snapshotReceiver(read);\n\n if (receiver === undefined || !sameSource(source, declared.from, receiver)) continue;\n\n report(declared, read);\n }\n };\n\n return {\n ':function'(node: TSESTree.Node): void {\n if (isFunction(node)) written.functions.push(node);\n },\n 'AwaitExpression, YieldExpression'(node: TSESTree.Node): void {\n written.barriers.push(node);\n },\n CallExpression(node): void {\n written.calls.push(node);\n },\n 'Program:exit'(): void {\n for (const call of written.calls) {\n const declared = effectRun(call);\n\n if (declared !== undefined) reportReads(declared);\n }\n },\n };\n },\n meta: {\n docs: {\n description: 'The run of an effect answers a value it was given; it does not read that value back.',\n },\n fixable: 'code',\n messages: {\n preferEffectCurrent:\n 'This reads the source of the effect it is already inside. The run carries that value as `execution.current`, which is the value this run was started for and the one `when` and `skipInitial` are written against; `getSnapshot()` here reads the source a second time and says nothing about which run this is (D296, D323).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'prefer-effect-current',\n});\n\nexport { preferEffectCurrent };\n"],"names":["within","node","outer","snapshotReceiver","callee","AST_NODE_TYPES","unwrap","sameSource","source","from","candidate","declared","variableOf","read","destructuredCurrent","pattern","property","propertyName","currentExpression","run","parameter","declaresWhen","options","record","findProperty","effectArguments","call","isFunction","preferEffectCurrent","createRule","context","written","effectRun","named","methodPath","isContextFunction","nested","fn","settled","barrier","report","replacement","fixer","reportReads","receiver"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prefer-effect-current.js","sources":["../../src/rules/prefer-effect-current.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 { declaredEffect } from '../effect-declarations';\nimport type { FunctionNode } from '../model-bindings';\nimport { isFunction, unwrap, variableOf } from '../model-bindings';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'preferEffectCurrent';\n\n/** What one file wrote, collected before any read is judged: the rule answers on `Program:exit` (D290). */\ntype Written = {\n readonly barriers: TSESTree.Node[];\n readonly calls: TSESTree.CallExpression[];\n readonly functions: FunctionNode[];\n};\n\n/** One `effect(source, run, options?)` declared on a context of this library, with what a rewrite of it needs. */\ntype Declared = { readonly fixable: boolean; readonly from: TSESTree.Identifier; readonly run: FunctionNode };\n\n/** `s.getSnapshot()`, and the receiver it was read on; a reference that is not called reads nothing. */\nfunction snapshotReceiver(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const { callee } = node;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return undefined;\n\n if (callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== 'getSnapshot') return undefined;\n\n return node.arguments.length === 0 ? unwrap(callee.object) : undefined;\n}\n\n/** The same source: a name is the binding it resolves to, and two unresolved names are the same spelling. */\nfunction sameSource(source: TSESLint.SourceCode, from: TSESTree.Identifier, candidate: TSESTree.Node): boolean {\n if (candidate.type !== AST_NODE_TYPES.Identifier) return false;\n\n const declared = variableOf(source, from);\n const read = variableOf(source, candidate);\n\n return declared === null && read === null ? from.name === candidate.name : declared === read;\n}\n\n/**\n * D379: the value is the first parameter of the run, so the word the fix writes is the name the author bound it\n * under. A run that destructured the value itself, or took no parameter at all, has no word for the whole of it, and\n * the report then carries no fix.\n */\nfunction currentExpression(run: FunctionNode): string | undefined {\n const parameter = run.params[0];\n\n return parameter?.type === AST_NODE_TYPES.Identifier ? parameter.name : undefined;\n}\n\n/**\n * D296, D379: `filter` selects values without restarting the run, so under it the source moves while the run keeps\n * going. The value the run was given is then the one it was started for and the snapshot is the newer one — two\n * different facts, and a silent rewrite between them is exactly what a fixable rule may not do.\n */\nfunction declaresFilter(options: TSESTree.CallExpressionArgument | undefined): boolean {\n if (options === undefined) return false;\n\n const record = unwrap(options);\n\n // A record assembled elsewhere may carry the predicate, and a rule that rewrites code assumes it does.\n if (record.type !== AST_NODE_TYPES.ObjectExpression) return true;\n\n return findProperty(record, 'filter') !== undefined;\n}\n\nconst preferEffectCurrent = createRule<[], MessageIds>({\n create(context) {\n const source = context.sourceCode;\n const written: Written = { barriers: [], calls: [], functions: [] };\n\n /**\n * This rule rewrites a read inside the run, so it asks for the two shapes a rewrite needs and nothing wider: a\n * source named by an identifier, to compare the read against, and the run written where the declaration takes it.\n */\n const effectRun = (call: TSESTree.CallExpression): Declared | undefined => {\n const declared = declaredEffect(source, call);\n\n if (declared === undefined || !declared.inline || declared.from.type !== AST_NODE_TYPES.Identifier) {\n return undefined;\n }\n\n return { fixable: !declaresFilter(declared.options), from: declared.from, run: declared.run };\n };\n /** A function written inside the run runs later than the run does, and a snapshot read there is its own. */\n const nested = (node: TSESTree.Node, run: FunctionNode): boolean =>\n written.functions.some(fn => fn !== run && within(fn, run) && within(node, fn));\n /**\n * After an `await` the two are no longer the same value: the parameter is what this run started with, the\n * snapshot is what the source holds now. The run usually restarts on a change, so they usually agree — and\n * «usually» is not a licence to rewrite, so past the first barrier the rule only says so.\n */\n const settled = (node: TSESTree.Node, run: FunctionNode): boolean =>\n written.barriers.some(barrier => within(barrier, run) && barrier.range[0] < node.range[0]);\n const report = (declared: Declared, read: TSESTree.CallExpression): void => {\n const replacement = currentExpression(declared.run);\n\n if (replacement === undefined || !declared.fixable || settled(read, declared.run)) {\n context.report({ messageId: 'preferEffectCurrent', node: read });\n\n return;\n }\n\n context.report({\n fix: fixer => fixer.replaceTextRange(read.range, replacement),\n messageId: 'preferEffectCurrent',\n node: read,\n });\n };\n const reportReads = (declared: Declared): void => {\n for (const read of written.calls) {\n if (!within(read, declared.run) || nested(read, declared.run)) continue;\n\n const receiver = snapshotReceiver(read);\n\n if (receiver === undefined || !sameSource(source, declared.from, receiver)) continue;\n\n report(declared, read);\n }\n };\n\n return {\n ':function'(node: TSESTree.Node): void {\n if (isFunction(node)) written.functions.push(node);\n },\n 'AwaitExpression, YieldExpression'(node: TSESTree.Node): void {\n written.barriers.push(node);\n },\n CallExpression(node): void {\n written.calls.push(node);\n },\n 'Program:exit'(): void {\n for (const call of written.calls) {\n const declared = effectRun(call);\n\n if (declared !== undefined) reportReads(declared);\n }\n },\n };\n },\n meta: {\n docs: {\n description: 'The run of an effect answers a value it was given; it does not read that value back.',\n },\n fixable: 'code',\n messages: {\n preferEffectCurrent:\n 'This reads the source of the effect it is already inside. The run receives that value as its first parameter, which is the value this run was started for and the one `filter` and `initial` are written against; `getSnapshot()` here reads the source a second time and says nothing about which run this is (D296, D323, D379).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'prefer-effect-current',\n});\n\nexport { preferEffectCurrent };\n"],"names":["snapshotReceiver","node","callee","AST_NODE_TYPES","unwrap","sameSource","source","from","candidate","declared","variableOf","read","currentExpression","run","parameter","declaresFilter","options","record","findProperty","preferEffectCurrent","createRule","context","written","effectRun","call","declaredEffect","nested","fn","within","settled","barrier","report","replacement","fixer","reportReads","receiver","isFunction"],"mappings":"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"}
|
package/oxlintrc.json
CHANGED
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
"opetope/no-command-in-deps": "error",
|
|
8
8
|
"opetope/no-internal-imports": "error",
|
|
9
9
|
"opetope/no-redundant-const-tuple": "error",
|
|
10
|
+
"opetope/no-retired-vocabulary": "error",
|
|
10
11
|
"opetope/no-snapshot-in-update": "error",
|
|
11
12
|
"opetope/no-snapshot-read-in-render": "error",
|
|
12
13
|
"opetope/no-subscribe-outside-models": "off",
|
|
14
|
+
"opetope/no-write-after-source-write": "error",
|
|
13
15
|
"opetope/prefer-effect-current": "error",
|
|
14
16
|
"opetope/prefer-model-selection": "off",
|
|
15
17
|
"opetope/require-declared-models": "error",
|
|
16
18
|
"opetope/require-literal-id": "error",
|
|
17
|
-
"opetope/
|
|
19
|
+
"opetope/enabled-predicate": "error"
|
|
18
20
|
}
|
|
19
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
type MessageIds = 'whenIsAsync' | 'whenReturnsBareSource' | 'whenReturnsSource';
|
|
3
|
-
declare const whenPredicate: TSESLint.RuleModule<MessageIds, [], unknown, TSESLint.RuleListener> & {
|
|
4
|
-
name: string;
|
|
5
|
-
};
|
|
6
|
-
export { whenPredicate };
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{AST_NODE_TYPES as i}from"@typescript-eslint/utils";import{propertyName as m,isFunctionLike as l,calleeName as y,patternKeys as c,memberRoot as h,memberFirstStep as S}from"../ast.js";import{createRule as I}from"../rule.js";const b=new Set(["pipe","register","slot"]),x=new Set(["exports","imports","own","read"]),w=new Set(["exports","imports","own"]);function T(n){const{parent:e}=n;if(e.type!==i.CallExpression||e.arguments.length<2)return!1;const t=y(e);return t!==void 0&&b.has(t)&&e.arguments.at(-1)===n}function d(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==i.ObjectPattern))return[...c(e)].some(t=>x.has(t))?e:void 0}function g(n){const[e]=n.params;if(!(n.params.length!==1||e?.type!==i.Identifier))return e}function A(n,e){const t=h(e),r=S(e);return t.type!==i.Identifier||t.name!==n.name?!1:r!==void 0&&w.has(r)}function E(n,e){const t=h(e);return t.type!==i.Identifier?!1:w.has(t.name)&&c(n).has(t.name)}function R(n,e){const t=d(n);if(t!==void 0)return E(t,e)?"read":void 0;const r=g(n);if(r!==void 0)return A(r,e)?`${r.name}.read`:void 0}function O(n,e){const t=e.properties.find(s=>{const a=m(s);return a===void 0||a>"read"});if(t!==void 0)return n.insertTextBefore(t,"read, ");const r=e.properties.at(-1);return r===void 0?void 0:n.insertTextAfter(r,", read")}function C(n,e){return n.params.some(t=>t.type===i.Identifier&&t.name===e.name)}function N(n,e){const{parent:t}=n;return t.type===i.ObjectExpression&&T(t)||d(e)!==void 0}const B=I({create(n){const e=(r,s)=>{const a=R(r,s);a!==void 0&&n.report({fix(p){const o=d(r),u=p.replaceText(s,`${a}(${n.sourceCode.getText(s)})`);if(o===void 0||c(o).has("read"))return[u];const f=O(p,o);return f===void 0?[u]:[u,f]},messageId:"whenReturnsSource",node:s})},t=r=>{if(r.async){n.report({messageId:"whenIsAsync",node:r});return}if(r.type!==i.ArrowFunctionExpression)return;const{body:s}=r;if(s.type===i.MemberExpression){e(r,s);return}s.type===i.Identifier&&!C(r,s)&&n.report({messageId:"whenReturnsBareSource",node:s})};return{Property(r){m(r)!=="when"||!l(r.value)||N(r,r.value)&&t(r.value)}}},meta:{docs:{description:"A contribution `when` predicate answers the visibility fact, not the source that carries it."},fixable:"code",messages:{whenIsAsync:"A contribution `when` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).",whenReturnsBareSource:"A contribution `when` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).",whenReturnsSource:"A contribution `when` predicate returns the source instead of the fact; read it with `read(...)` (D220)."},schema:[],type:"problem"},name:"when-predicate"});export{B as whenPredicate};
|
|
2
|
-
//# sourceMappingURL=when-predicate.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"when-predicate.js","sources":["../../src/rules/when-predicate.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport type { FunctionLike } from '../ast';\nimport { calleeName, isFunctionLike, memberFirstStep, memberRoot, patternKeys, propertyName } from '../ast';\nimport { createRule } from '../rule';\n\ntype MessageIds = 'whenIsAsync' | 'whenReturnsBareSource' | 'whenReturnsSource';\n\nconst CONTRIBUTION_BUILDERS = new Set(['pipe', 'register', 'slot']);\nconst CONTEXT_KEYS = new Set(['exports', 'imports', 'own', 'read']);\nconst CONTEXT_SOURCES = new Set(['exports', 'imports', 'own']);\n\n/** The options object of `slot(target, contribution, {…})`, `pipe(target, {fold}, {…})`, `register(…)`. */\nfunction isBuilderOptions(node: TSESTree.ObjectExpression): boolean {\n const { parent } = node;\n\n if (parent.type !== AST_NODE_TYPES.CallExpression || parent.arguments.length < 2) return false;\n\n const name = calleeName(parent);\n\n return name !== undefined && CONTRIBUTION_BUILDERS.has(name) && parent.arguments.at(-1) === node;\n}\n\n/** The destructured evaluation context of a contribution predicate: `({ exports, imports, own, read })`. */\nfunction contextPattern(fn: FunctionLike): TSESTree.ObjectPattern | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.ObjectPattern) return undefined;\n\n return [...patternKeys(parameter)].some(key => CONTEXT_KEYS.has(key)) ? parameter : undefined;\n}\n\n/** The same context taken as one named parameter: `context => context.imports.platform.submitAllowed`. */\nfunction contextIdentifier(fn: FunctionLike): TSESTree.Identifier | undefined {\n const [parameter] = fn.params;\n\n if (fn.params.length !== 1 || parameter?.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return parameter;\n}\n\nfunction isNamedContextAccess(parameter: TSESTree.Identifier, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n const step = memberFirstStep(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier || root.name !== parameter.name) return false;\n\n return step !== undefined && CONTEXT_SOURCES.has(step);\n}\n\nfunction isDestructuredContextAccess(pattern: TSESTree.ObjectPattern, body: TSESTree.MemberExpression): boolean {\n const root = memberRoot(body);\n\n if (root.type !== AST_NODE_TYPES.Identifier) return false;\n\n return CONTEXT_SOURCES.has(root.name) && patternKeys(pattern).has(root.name);\n}\n\n/** The reader a fix would call, or `undefined` when the body reaches something other than the instance context. */\nfunction readerFor(fn: FunctionLike, body: TSESTree.MemberExpression): string | undefined {\n const pattern = contextPattern(fn);\n\n if (pattern !== undefined) return isDestructuredContextAccess(pattern, body) ? 'read' : undefined;\n\n const parameter = contextIdentifier(fn);\n\n if (parameter === undefined) return undefined;\n\n return isNamedContextAccess(parameter, body) ? `${parameter.name}.read` : undefined;\n}\n\n/** `read` joins the destructured context in place, so the fixed predicate keeps the order authors write. */\nfunction insertReadKey(fixer: TSESLint.RuleFixer, pattern: TSESTree.ObjectPattern): TSESLint.RuleFix | undefined {\n const successor = pattern.properties.find(property => {\n const name = propertyName(property);\n\n return name === undefined || name > 'read';\n });\n\n if (successor !== undefined) return fixer.insertTextBefore(successor, 'read, ');\n\n const last = pattern.properties.at(-1);\n\n return last === undefined ? undefined : fixer.insertTextAfter(last, ', read');\n}\n\nfunction isOwnParameter(fn: FunctionLike, identifier: TSESTree.Identifier): boolean {\n return fn.params.some(\n parameter => parameter.type === AST_NODE_TYPES.Identifier && parameter.name === identifier.name,\n );\n}\n\nfunction isContributionWhen(property: TSESTree.Property, fn: FunctionLike): boolean {\n const { parent } = property;\n\n return (\n (parent.type === AST_NODE_TYPES.ObjectExpression && isBuilderOptions(parent)) || contextPattern(fn) !== undefined\n );\n}\n\nconst whenPredicate = createRule<[], MessageIds>({\n create(context) {\n const reportSource = (fn: FunctionLike, body: TSESTree.MemberExpression): void => {\n const reader = readerFor(fn, body);\n\n if (reader === undefined) return;\n\n context.report({\n fix(fixer) {\n const pattern = contextPattern(fn);\n const wrapped = fixer.replaceText(body, `${reader}(${context.sourceCode.getText(body)})`);\n\n if (pattern === undefined || patternKeys(pattern).has('read')) return [wrapped];\n\n const inserted = insertReadKey(fixer, pattern);\n\n return inserted === undefined ? [wrapped] : [wrapped, inserted];\n },\n messageId: 'whenReturnsSource',\n node: body,\n });\n };\n\n const check = (fn: FunctionLike): void => {\n if (fn.async) {\n context.report({ messageId: 'whenIsAsync', node: fn });\n\n return;\n }\n\n if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression) return;\n\n const { body } = fn;\n\n if (body.type === AST_NODE_TYPES.MemberExpression) {\n reportSource(fn, body);\n\n return;\n }\n\n if (body.type === AST_NODE_TYPES.Identifier && !isOwnParameter(fn, body)) {\n context.report({ messageId: 'whenReturnsBareSource', node: body });\n }\n };\n\n return {\n Property(node): void {\n if (propertyName(node) !== 'when' || !isFunctionLike(node.value)) return;\n\n if (isContributionWhen(node, node.value)) check(node.value);\n },\n };\n },\n meta: {\n docs: {\n description: 'A contribution `when` predicate answers the visibility fact, not the source that carries it.',\n },\n fixable: 'code',\n messages: {\n whenIsAsync:\n 'A contribution `when` predicate answers a boolean synchronously; an asynchronous one answers a promise (D220).',\n whenReturnsBareSource:\n 'A contribution `when` takes a `Readable<boolean>` as it is; a predicate must answer the fact with `read(...)` (D220).',\n whenReturnsSource:\n 'A contribution `when` predicate returns the source instead of the fact; read it with `read(...)` (D220).',\n },\n schema: [],\n type: 'problem',\n },\n name: 'when-predicate',\n});\n\nexport { whenPredicate };\n"],"names":["CONTRIBUTION_BUILDERS","CONTEXT_KEYS","CONTEXT_SOURCES","isBuilderOptions","node","parent","AST_NODE_TYPES","name","calleeName","contextPattern","fn","parameter","patternKeys","key","contextIdentifier","isNamedContextAccess","body","root","memberRoot","step","memberFirstStep","isDestructuredContextAccess","pattern","readerFor","insertReadKey","fixer","successor","property","propertyName","last","isOwnParameter","identifier","isContributionWhen","whenPredicate","createRule","context","reportSource","reader","wrapped","inserted","check","isFunctionLike"],"mappings":"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"}
|