@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +667 -4
  2. package/README.md +69 -596
  3. package/dist/ast.d.ts +3 -1
  4. package/dist/ast.js +1 -1
  5. package/dist/ast.js.map +1 -1
  6. package/dist/command-hooks.d.ts +2 -2
  7. package/dist/command-hooks.js +1 -1
  8. package/dist/command-hooks.js.map +1 -1
  9. package/dist/context-members.d.ts +22 -0
  10. package/dist/context-members.js +2 -0
  11. package/dist/context-members.js.map +1 -0
  12. package/dist/declaration-ingress.d.ts +10 -2
  13. package/dist/declaration-ingress.js +1 -1
  14. package/dist/declaration-ingress.js.map +1 -1
  15. package/dist/effect-declarations.d.ts +20 -0
  16. package/dist/effect-declarations.js +2 -0
  17. package/dist/effect-declarations.js.map +1 -0
  18. package/dist/index.d.ts +11 -3
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/retired-vocabulary.d.ts +31 -0
  22. package/dist/retired-vocabulary.js +2 -0
  23. package/dist/retired-vocabulary.js.map +1 -0
  24. package/dist/rules/capture-command-cleanup.js +1 -1
  25. package/dist/rules/capture-command-cleanup.js.map +1 -1
  26. package/dist/rules/define-feature-property-order.js +1 -1
  27. package/dist/rules/define-feature-property-order.js.map +1 -1
  28. package/dist/rules/enabled-predicate.d.ts +6 -0
  29. package/dist/rules/enabled-predicate.js +2 -0
  30. package/dist/rules/enabled-predicate.js.map +1 -0
  31. package/dist/rules/no-command-in-deps.js +1 -1
  32. package/dist/rules/no-command-in-deps.js.map +1 -1
  33. package/dist/rules/no-internal-imports.js +1 -1
  34. package/dist/rules/no-internal-imports.js.map +1 -1
  35. package/dist/rules/no-retired-vocabulary.d.ts +5 -0
  36. package/dist/rules/no-retired-vocabulary.js +2 -0
  37. package/dist/rules/no-retired-vocabulary.js.map +1 -0
  38. package/dist/rules/no-write-after-source-write.d.ts +6 -0
  39. package/dist/rules/no-write-after-source-write.js +2 -0
  40. package/dist/rules/no-write-after-source-write.js.map +1 -0
  41. package/dist/rules/prefer-effect-current.js +1 -1
  42. package/dist/rules/prefer-effect-current.js.map +1 -1
  43. package/oxlintrc.json +3 -1
  44. package/package.json +1 -2
  45. package/README.ru.md +0 -648
  46. package/dist/rules/when-predicate.d.ts +0 -6
  47. package/dist/rules/when-predicate.js +0 -2
  48. package/dist/rules/when-predicate.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as s}from"@typescript-eslint/utils";import{findProperty as y,propertyName as b}from"../ast.js";import{methodPath as x,isContextFunction as w}from"../declaration-ingress.js";import{isFunction as c,unwrap as d,variableOf as p}from"../model-bindings.js";import{createRule as E}from"../rule.js";function a(r,e){return r.range[0]>=e.range[0]&&r.range[1]<=e.range[1]}function v(r){const{callee:e}=r;if(!(e.type!==s.MemberExpression||e.computed)&&!(e.property.type!==s.Identifier||e.property.name!=="getSnapshot"))return r.arguments.length===0?d(e.object):void 0}function C(r,e,i){if(i.type!==s.Identifier)return!1;const f=p(r,e),o=p(r,i);return f===null&&o===null?e.name===i.name:f===o}function I(r){for(const e of r.properties)if(!(e.type!==s.Property||b(e)!=="current"))return e.value.type===s.Identifier?e.value.name:void 0}function P(r){const e=r.params[0];if(e!==void 0){if(e.type===s.Identifier)return`${e.name}.current`;if(e.type===s.ObjectPattern)return I(e)}}function T(r){if(r===void 0)return!1;const e=d(r);return e.type!==s.ObjectExpression?!0:y(e,"when")!==void 0}function R(r){const e=r.arguments[0],i=r.arguments[1];if(!(e===void 0||e.type!==s.Identifier)&&!(i===void 0||!c(i)))return{fixable:!T(r.arguments[2]),from:e,run:i}}const S=E({create(r){const e=r.sourceCode,i={barriers:[],calls:[],functions:[]},f=n=>{const t=x(e,n.callee);if(t?.method!=="effect")return;const u=R(n);if(u!==void 0)return w(e,t.fn)?u:void 0},o=(n,t)=>i.functions.some(u=>u!==t&&a(u,t)&&a(n,u)),m=(n,t)=>i.barriers.some(u=>a(u,t)&&u.range[0]<n.range[0]),l=(n,t)=>{const u=P(n.run);if(u===void 0||!n.fixable||m(t,n.run)){r.report({messageId:"preferEffectCurrent",node:t});return}r.report({fix:g=>g.replaceTextRange(t.range,u),messageId:"preferEffectCurrent",node:t})},h=n=>{for(const t of i.calls){if(!a(t,n.run)||o(t,n.run))continue;const u=v(t);u===void 0||!C(e,n.from,u)||l(n,t)}};return{":function"(n){c(n)&&i.functions.push(n)},"AwaitExpression, YieldExpression"(n){i.barriers.push(n)},CallExpression(n){i.calls.push(n)},"Program:exit"(){for(const n of i.calls){const t=f(n);t!==void 0&&h(t)}}}},meta:{docs:{description:"The run of an effect answers a value it was given; it does not read that value back."},fixable:"code",messages:{preferEffectCurrent:"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)."},schema:[],type:"problem"},name:"prefer-effect-current"});export{S as preferEffectCurrent};
1
+ import{AST_NODE_TYPES as o}from"@typescript-eslint/utils";import{within as u,findProperty as g}from"../ast.js";import{declaredEffect as E}from"../effect-declarations.js";import{isFunction as b,unwrap as c,variableOf as p}from"../model-bindings.js";import{createRule as y}from"../rule.js";function x(t){const{callee:n}=t;if(!(n.type!==o.MemberExpression||n.computed)&&!(n.property.type!==o.Identifier||n.property.name!=="getSnapshot"))return t.arguments.length===0?c(n.object):void 0}function w(t,n,s){if(s.type!==o.Identifier)return!1;const f=p(t,n),a=p(t,s);return f===null&&a===null?n.name===s.name:f===a}function v(t){const n=t.params[0];return n?.type===o.Identifier?n.name:void 0}function C(t){if(t===void 0)return!1;const n=c(t);return n.type!==o.ObjectExpression?!0:g(n,"filter")!==void 0}const I=y({create(t){const n=t.sourceCode,s={barriers:[],calls:[],functions:[]},f=r=>{const e=E(n,r);if(!(e===void 0||!e.inline||e.from.type!==o.Identifier))return{fixable:!C(e.options),from:e.from,run:e.run}},a=(r,e)=>s.functions.some(i=>i!==e&&u(i,e)&&u(r,i)),d=(r,e)=>s.barriers.some(i=>u(i,e)&&i.range[0]<r.range[0]),l=(r,e)=>{const i=v(r.run);if(i===void 0||!r.fixable||d(e,r.run)){t.report({messageId:"preferEffectCurrent",node:e});return}t.report({fix:h=>h.replaceTextRange(e.range,i),messageId:"preferEffectCurrent",node:e})},m=r=>{for(const e of s.calls){if(!u(e,r.run)||a(e,r.run))continue;const i=x(e);i===void 0||!w(n,r.from,i)||l(r,e)}};return{":function"(r){b(r)&&s.functions.push(r)},"AwaitExpression, YieldExpression"(r){s.barriers.push(r)},CallExpression(r){s.calls.push(r)},"Program:exit"(){for(const r of s.calls){const e=f(r);e!==void 0&&m(e)}}}},meta:{docs:{description:"The run of an effect answers a value it was given; it does not read that value back."},fixable:"code",messages:{preferEffectCurrent:"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)."},schema:[],type:"problem"},name:"prefer-effect-current"});export{I as preferEffectCurrent};
2
2
  //# sourceMappingURL=prefer-effect-current.js.map
@@ -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":"yTAqBA,SAASA,EAAOC,EAAqBC,EAAoB,CACvD,OAAOD,EAAK,MAAM,CAAC,GAAKC,EAAM,MAAM,CAAC,GAAKD,EAAK,MAAM,CAAC,GAAKC,EAAM,MAAM,CAAC,CAC1E,CAGA,SAASC,EAAiBF,EAA6B,CACrD,KAAM,CAAE,OAAAG,CAAM,EAAKH,EAEnB,GAAI,EAAAG,EAAO,OAASC,EAAe,kBAAoBD,EAAO,WAE1D,EAAAA,EAAO,SAAS,OAASC,EAAe,YAAcD,EAAO,SAAS,OAAS,eAEnF,OAAOH,EAAK,UAAU,SAAW,EAAIK,EAAOF,EAAO,MAAM,EAAI,MAC/D,CAGA,SAASG,EAAWC,EAA6BC,EAA2BC,EAAwB,CAClG,GAAIA,EAAU,OAASL,EAAe,WAAY,MAAO,GAEzD,MAAMM,EAAWC,EAAWJ,EAAQC,CAAI,EAClCI,EAAOD,EAAWJ,EAAQE,CAAS,EAEzC,OAAOC,IAAa,MAAQE,IAAS,KAAOJ,EAAK,OAASC,EAAU,KAAOC,IAAaE,CAC1F,CAGA,SAASC,EAAoBC,EAA+B,CAC1D,UAAWC,KAAYD,EAAQ,WAC7B,GAAI,EAAAC,EAAS,OAASX,EAAe,UAAYY,EAAaD,CAAQ,IAAM,WAE5E,OAAOA,EAAS,MAAM,OAASX,EAAe,WAAaW,EAAS,MAAM,KAAO,MAIrF,CAMA,SAASE,EAAkBC,EAAiB,CAC1C,MAAMC,EAAYD,EAAI,OAAO,CAAC,EAE9B,GAAIC,IAAc,OAElB,IAAIA,EAAU,OAASf,EAAe,WAAY,MAAO,GAAGe,EAAU,IAAI,WAE1E,GAAIA,EAAU,OAASf,EAAe,cAEtC,OAAOS,EAAoBM,CAAS,EACtC,CAOA,SAASC,EAAaC,EAAoD,CACxE,GAAIA,IAAY,OAAW,MAAO,GAElC,MAAMC,EAASjB,EAAOgB,CAAO,EAG7B,OAAIC,EAAO,OAASlB,EAAe,iBAAyB,GAErDmB,EAAaD,EAAQ,MAAM,IAAM,MAC1C,CAGA,SAASE,EAAgBC,EAA6B,CACpD,MAAMjB,EAAOiB,EAAK,UAAU,CAAC,EACvBP,EAAMO,EAAK,UAAU,CAAC,EAE5B,GAAI,EAAAjB,IAAS,QAAaA,EAAK,OAASJ,EAAe,aAEnD,EAAAc,IAAQ,QAAa,CAACQ,EAAWR,CAAG,GAExC,MAAO,CAAE,QAAS,CAACE,EAAaK,EAAK,UAAU,CAAC,CAAC,EAAG,KAAAjB,EAAM,IAAAU,CAAG,CAC/D,CAEA,MAAMS,EAAsBC,EAA2B,CACrD,OAAOC,EAAO,CACZ,MAAMtB,EAASsB,EAAQ,WACjBC,EAAmB,CAAE,SAAU,CAAA,EAAI,MAAO,CAAA,EAAI,UAAW,EAAE,EAE3DC,EAAaN,GAAuD,CACxE,MAAMO,EAAQC,EAAW1B,EAAQkB,EAAK,MAAM,EAE5C,GAAIO,GAAO,SAAW,SAAU,OAEhC,MAAMtB,EAAWc,EAAgBC,CAAI,EAErC,GAAIf,IAAa,OAEjB,OAAOwB,EAAkB3B,EAAQyB,EAAM,EAAE,EAAItB,EAAW,MAC1D,EAEMyB,EAAS,CAACnC,EAAqBkB,IACnCY,EAAQ,UAAU,KAAKM,GAAMA,IAAOlB,GAAOnB,EAAOqC,EAAIlB,CAAG,GAAKnB,EAAOC,EAAMoC,CAAE,CAAC,EAM1EC,EAAU,CAACrC,EAAqBkB,IACpCY,EAAQ,SAAS,KAAKQ,GAAWvC,EAAOuC,EAASpB,CAAG,GAAKoB,EAAQ,MAAM,CAAC,EAAItC,EAAK,MAAM,CAAC,CAAC,EACrFuC,EAAS,CAAC7B,EAAoBE,IAAuC,CACzE,MAAM4B,EAAcvB,EAAkBP,EAAS,GAAG,EAElD,GAAI8B,IAAgB,QAAa,CAAC9B,EAAS,SAAW2B,EAAQzB,EAAMF,EAAS,GAAG,EAAG,CACjFmB,EAAQ,OAAO,CAAE,UAAW,sBAAuB,KAAMjB,EAAM,EAE/D,MACF,CAEAiB,EAAQ,OAAO,CACb,IAAKY,GAASA,EAAM,iBAAiB7B,EAAK,MAAO4B,CAAW,EAC5D,UAAW,sBACX,KAAM5B,CACP,CAAA,CACH,EACM8B,EAAehC,GAA4B,CAC/C,UAAWE,KAAQkB,EAAQ,MAAO,CAChC,GAAI,CAAC/B,EAAOa,EAAMF,EAAS,GAAG,GAAKyB,EAAOvB,EAAMF,EAAS,GAAG,EAAG,SAE/D,MAAMiC,EAAWzC,EAAiBU,CAAI,EAElC+B,IAAa,QAAa,CAACrC,EAAWC,EAAQG,EAAS,KAAMiC,CAAQ,GAEzEJ,EAAO7B,EAAUE,CAAI,CACvB,CACF,EAEA,MAAO,CACL,YAAYZ,EAAmB,CACzB0B,EAAW1B,CAAI,GAAG8B,EAAQ,UAAU,KAAK9B,CAAI,CACnD,EACA,mCAAmCA,EAAmB,CACpD8B,EAAQ,SAAS,KAAK9B,CAAI,CAC5B,EACA,eAAeA,EAAI,CACjB8B,EAAQ,MAAM,KAAK9B,CAAI,CACzB,EACA,gBAAc,CACZ,UAAWyB,KAAQK,EAAQ,MAAO,CAChC,MAAMpB,EAAWqB,EAAUN,CAAI,EAE3Bf,IAAa,QAAWgC,EAAYhC,CAAQ,CAClD,CACF,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,sFACd,EACD,QAAS,OACT,SAAU,CACR,oBACE,+TACH,EACD,OAAQ,CAAA,EACR,KAAM,SACP,EACD,KAAM,uBACP,CAAA"}
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/when-predicate": "error"
19
+ "opetope/enabled-predicate": "error"
18
20
  }
19
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/lint",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },
@@ -9,7 +9,6 @@
9
9
  "dist",
10
10
  "oxlintrc.json",
11
11
  "README.md",
12
- "README.ru.md",
13
12
  "LICENSE",
14
13
  "CHANGELOG.md"
15
14
  ],