@opetope/lint 0.7.0 → 0.8.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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # @opetope/lint
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a36f487: Restore two writes that the one calling shape had closed off in types only; runtime behaviour is unchanged.
8
+
9
+ `ctx.update` is two overloads instead of one union. As a union the value half `PlainStateValue<Value>` stayed a deferred conditional, and a deferred target cannot discriminate a union state, so naming one member of a state whose other members lack its fields — writing `{ error, kind: 'failed' }` into `{ kind: 'idle' | 'ready' } | { error: unknown; kind: 'failed' }` — stopped compiling. Alone in the value overload the same conditional resolves, so D282 still holds: a function in that position is the updater, and a function-valued state is written through one. The updater's return is checked against the state, so a branch returning a narrower literal needs a return annotation.
10
+
11
+ `ctx.call(run, options)` treats the body and its modifiers as one inference pool again. D279 lets either side declare the input, but `ModelCallOptions<NoInfer<Input>>` removed the options as an inference site, so a call whose input is declared by `once` or `dedupe` left an unannotated `run` parameter implicitly `any` and failed over to the selection overload with an unrelated error. A body that declares its own input was never affected.
12
+
13
+ ## 0.8.0
14
+
15
+ ### Minor Changes
16
+
17
+ - dee6f21: Let a feature answer a condition. `defineCondition(id)` is now only a name; the fact behind it comes either from a feature of the world, through the fifth word of `provides` — `condition(target, ({ own }) => readable)` — or from the host, and never from both. The answer reads `own`, so a fact no longer has to widen `exports` to gate anything, and the name can be declared in a shared module that imports no feature.
18
+
19
+ One id keeps at most one answer: two answering features, a feature waiting for the condition it answers, an answer shorter-lived than what it gates, and an answer to a condition that already names its own source are refused when the application compiles. A lazy header declares the condition exactly as it declares a port, and its body is held to that declaration.
20
+
21
+ `openApplication` and `createScenario` take `conditions` as an array of `bind(condition, readable)` — the same word `imports` uses. Identity comes from the token instead of a repeated id string, and the type asks for exactly the host conditions the world does not answer itself, naming a missing or unexpected binding by its id. `defineCondition(id, { from, select })` stays for the other relationship: a consumer reading one feature's exports, declared where that reading belongs.
22
+
23
+ - aff7b5d: Give every authoring verb one shape: `verb(subjects…, behaviours…, options?)` (D279). What a step works on and the work itself are arguments; only modifiers stay in the trailing record. This covers `call`, `effect`, `event`, `resource`, `stream`, the `scope` openers, `derive`, `collection`, `lane` and `useSelector`; `key` became a modifier, and a target that already is a request key names itself. `attach` and `pipe` keep their contract-declaring records (D223).
24
+
25
+ Name every definition the same way: `defineX(id, options?)` (D280), including `defineFeature` and `defineApplication`, whose `id` left the sections record. Move `reporter` from `defineApplication` to `openApplication` and `createScenario`, and make an empty `imports` omissible on both `open*` verbs (D281). Add the updater form `ctx.update(state, previous => next)` (D282) and rename `CommandHook.result` to `lastResult` (D283). No compatibility aliases: packages, examples, guides and the ESLint rules move together.
26
+
3
27
  ## 0.7.0
4
28
 
5
29
  No changes in this release.
package/README.md CHANGED
@@ -123,10 +123,11 @@ the positional `(current, previous)` of `effect`, or the source value of `scope.
123
123
 
124
124
  ### `define-feature-property-order`
125
125
 
126
- A feature declares its sections in one order: `id`, `imports`, `requires`, `own`, `exports`, `provides`, `when`,
127
- and the `body` loader that replaces the last three stages (spec §2.1, D186). The order is the reading order of the
128
- declaration — identity, the edges, the stages, then the lifetime and the loader — so a reader finds a section by
129
- position, and a key sorter can be configured to hold the same order instead of a second one.
126
+ A feature declares its sections in one order: `imports`, `requires`, `own`, `exports`, `provides`, `when`, and the
127
+ `body` loader that replaces the last three stages (spec §2.1, D186). The identity is not among them — a feature
128
+ names itself with its first argument (D280). The order is the reading order of the declaration — the edges, the
129
+ stages, then the lifetime and the loader — so a reader finds a section by position, and a key sorter can be
130
+ configured to hold the same order instead of a second one.
130
131
 
131
132
  The fix reorders the properties. It stands down when a comment sits between two sections, because such a comment
132
133
  belongs to neither and reordering would move it away from the line it explains; a comment inside a section travels
@@ -147,8 +148,7 @@ const Form = () => {
147
148
  };
148
149
  const DeclaredForm = requiresModels([FormActions])(Form);
149
150
 
150
- defineFeature({
151
- id: 'example.form',
151
+ defineFeature('example.form', {
152
152
  provides: ({ slot }) => ({
153
153
  form: slot(FormSlot, ({ model }) => ({
154
154
  Component: DeclaredForm,
@@ -180,8 +180,7 @@ choice. Types and runtime authority checks continue to apply.
180
180
  A declaration names itself with a string that is written, not built where the declaration stands. The rule reads
181
181
  every declaration of the public vocabulary that names itself — `defineFeature`, `defineApplication`,
182
182
  `defineCondition`, `defineHostContract`, `defineModel`, `definePort`, `defineSlot`, `defineSwitchSlot`,
183
- `definePipe` and `defineRegistry`. `defineHostContract`, `defineModel` and `definePort` take the id as their first
184
- argument; the rest take the `id` key of their options.
183
+ `definePipe` and `defineRegistry`. D280 gives them one form: the id is the first argument of every one of them.
185
184
 
186
185
  Written means a string literal, a template with no expressions, a name that resolves to an import or to a `const`
187
186
  of the same module, and a property read from such a name. Built means anything the call site assembles: a template
package/README.ru.md CHANGED
@@ -124,10 +124,11 @@ Node. Применяйте конфигурацию ко всем использ
124
124
 
125
125
  ### `define-feature-property-order`
126
126
 
127
- Фича объявляет свои секции в одном порядке: `id`, `imports`, `requires`, `own`, `exports`, `provides`, `when` и
128
- загрузчик `body`, который заменяет три последние стадии (spec §2.1, D186). Порядок совпадает с порядком чтения
129
- объявления — идентичность, рёбра, стадии, затем время жизни и загрузчик, — поэтому читатель находит секцию по её
130
- месту, а сортировщик ключей можно настроить на тот же порядок вместо второго.
127
+ Фича объявляет свои секции в одном порядке: `imports`, `requires`, `own`, `exports`, `provides`, `when` и
128
+ загрузчик `body`, который заменяет три последние стадии (spec §2.1, D186). Идентичности среди них нет — фича
129
+ называет себя первым аргументом (D280). Порядок совпадает с порядком чтения объявления — рёбра, стадии, затем
130
+ время жизни и загрузчик, — поэтому читатель находит секцию по её месту, а сортировщик ключей можно настроить на
131
+ тот же порядок вместо второго.
131
132
 
132
133
  Фикс переставляет свойства. Он отступает, если между двумя секциями стоит комментарий: такой комментарий не
133
134
  принадлежит ни одной из них, и перестановка увела бы его от строки, которую он объясняет; комментарий внутри секции
@@ -148,8 +149,7 @@ const Form = () => {
148
149
  };
149
150
  const DeclaredForm = requiresModels([FormActions])(Form);
150
151
 
151
- defineFeature({
152
- id: 'example.form',
152
+ defineFeature('example.form', {
153
153
  provides: ({ slot }) => ({
154
154
  form: slot(FormSlot, ({ model }) => ({
155
155
  Component: DeclaredForm,
@@ -181,8 +181,7 @@ inline и именованные компоненты. Bindings сравнива
181
181
  Объявление называет себя строкой, которая написана, а не собрана там, где стоит объявление. Правило читает каждое
182
182
  объявление публичного словаря, которое называет себя: `defineFeature`, `defineApplication`, `defineCondition`,
183
183
  `defineHostContract`, `defineModel`, `definePort`, `defineSlot`, `defineSwitchSlot`, `definePipe` и
184
- `defineRegistry`. У `defineHostContract`, `defineModel` и `definePort` id — первый аргумент, у остальных — ключ
185
- `id` в опциях.
184
+ `defineRegistry`. D280 даёт им одну форму: id — первый аргумент у каждого.
186
185
 
187
186
  Написана — это строковый литерал, шаблон без выражений, имя, которое разрешается в импорт или в `const` того же
188
187
  модуля, и чтение поля у такого имени. Собрана — всё, что складывает место вызова: шаблон с выражением, конкатенация,
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as o}from"@typescript-eslint/utils";import{calleeName as d,findProperty as f}from"./ast.js";const r=new Map([["defineApplication","options"],["defineCondition","options"],["defineFeature","options"],["defineHostContract","argument"],["defineModel","argument"],["definePipe","options"],["definePort","argument"],["defineRegistry","options"],["defineSlot","options"],["defineSwitchSlot","options"]]);function u(e){const[n]=e.arguments;if(n?.type===o.ObjectExpression)return f(n,"id")?.value}function s(e){const[n]=e.arguments;if(!(n===void 0||n.type===o.SpreadElement))return n}function p(e){const n=d(e),i=n===void 0?void 0:r.get(n);if(n===void 0||i===void 0)return;const t=i==="options"?u(e):s(e);return t===void 0?void 0:{id:t,name:n}}export{p as declarationOf};
1
+ import{AST_NODE_TYPES as t}from"@typescript-eslint/utils";import{calleeName as d}from"./ast.js";const f=new Set(["defineApplication","defineCondition","defineFeature","defineHostContract","defineModel","definePipe","definePort","defineRegistry","defineSlot","defineSwitchSlot"]);function r(n){const[e]=n.arguments;if(!(e===void 0||e.type===t.SpreadElement))return e}function o(n){const e=d(n);if(e===void 0||!f.has(e))return;const i=r(n);return i===void 0?void 0:{id:i,name:e}}export{o as declarationOf};
2
2
  //# sourceMappingURL=declarations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"declarations.js","sources":["../src/declarations.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName, findProperty } from './ast';\n\n/** Where a declaration carries its id: as its first argument, or as the `id` key of its options. */\ntype IdLocation = 'argument' | 'options';\n\n/**\n * Every declaration of the public vocabulary that names itself, taken from the three safe entries: three of them\n * read the id as their first argument, the rest as the `id` key of their options. The kernel's own `defineModule`,\n * `defineCallTarget` and `defineCallLane` are absent because an author never writes them.\n */\nconst DECLARATIONS = new Map<string, IdLocation>([\n ['defineApplication', 'options'],\n ['defineCondition', 'options'],\n ['defineFeature', 'options'],\n ['defineHostContract', 'argument'],\n ['defineModel', 'argument'],\n ['definePipe', 'options'],\n ['definePort', 'argument'],\n ['defineRegistry', 'options'],\n ['defineSlot', 'options'],\n ['defineSwitchSlot', 'options'],\n]);\n\ntype Declaration = {\n readonly id: TSESTree.Node;\n readonly name: string;\n};\n\nfunction idOfOptions(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const [options] = node.arguments;\n\n if (options?.type !== AST_NODE_TYPES.ObjectExpression) return undefined;\n\n return findProperty(options, 'id')?.value;\n}\n\nfunction idOfArgument(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const [first] = node.arguments;\n\n if (first === undefined || first.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n return first;\n}\n\n/** The name and the id expression of a declaration call, or `undefined` when the call declares nothing. */\nfunction declarationOf(node: TSESTree.CallExpression): Declaration | undefined {\n const name = calleeName(node);\n const location = name === undefined ? undefined : DECLARATIONS.get(name);\n\n if (name === undefined || location === undefined) return undefined;\n\n const id = location === 'options' ? idOfOptions(node) : idOfArgument(node);\n\n return id === undefined ? undefined : { id, name };\n}\n\nexport { declarationOf };\n"],"names":["DECLARATIONS","idOfOptions","node","options","AST_NODE_TYPES","findProperty","idOfArgument","first","declarationOf","name","calleeName","location","id"],"mappings":"kHAaA,MAAMA,EAAe,IAAI,IAAwB,CAC/C,CAAC,oBAAqB,SAAS,EAC/B,CAAC,kBAAmB,SAAS,EAC7B,CAAC,gBAAiB,SAAS,EAC3B,CAAC,qBAAsB,UAAU,EACjC,CAAC,cAAe,UAAU,EAC1B,CAAC,aAAc,SAAS,EACxB,CAAC,aAAc,UAAU,EACzB,CAAC,iBAAkB,SAAS,EAC5B,CAAC,aAAc,SAAS,EACxB,CAAC,mBAAoB,SAAS,CAC/B,CAAA,EAOD,SAASC,EAAYC,EAA6B,CAChD,KAAM,CAACC,CAAO,EAAID,EAAK,UAEvB,GAAIC,GAAS,OAASC,EAAe,iBAErC,OAAOC,EAAaF,EAAS,IAAI,GAAG,KACtC,CAEA,SAASG,EAAaJ,EAA6B,CACjD,KAAM,CAACK,CAAK,EAAIL,EAAK,UAErB,GAAI,EAAAK,IAAU,QAAaA,EAAM,OAASH,EAAe,eAEzD,OAAOG,CACT,CAGA,SAASC,EAAcN,EAA6B,CAClD,MAAMO,EAAOC,EAAWR,CAAI,EACtBS,EAAWF,IAAS,OAAY,OAAYT,EAAa,IAAIS,CAAI,EAEvE,GAAIA,IAAS,QAAaE,IAAa,OAAW,OAElD,MAAMC,EAAKD,IAAa,UAAYV,EAAYC,CAAI,EAAII,EAAaJ,CAAI,EAEzE,OAAOU,IAAO,OAAY,OAAY,CAAE,GAAAA,EAAI,KAAAH,CAAI,CAClD"}
1
+ {"version":3,"file":"declarations.js","sources":["../src/declarations.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nimport { calleeName } from './ast';\n\n/**\n * Every declaration of the public vocabulary that names itself, taken from the three safe entries. D280 gives them\n * one form: the id is the first argument, whatever else the declaration carries. The kernel's own `defineModule`,\n * `defineCallTarget` and `defineCallLane` are absent because an author never writes them.\n */\nconst DECLARATIONS = new Set([\n 'defineApplication',\n 'defineCondition',\n 'defineFeature',\n 'defineHostContract',\n 'defineModel',\n 'definePipe',\n 'definePort',\n 'defineRegistry',\n 'defineSlot',\n 'defineSwitchSlot',\n]);\n\ntype Declaration = {\n readonly id: TSESTree.Node;\n readonly name: string;\n};\n\nfunction idOfArgument(node: TSESTree.CallExpression): TSESTree.Node | undefined {\n const [first] = node.arguments;\n\n if (first === undefined || first.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n return first;\n}\n\n/** The name and the id expression of a declaration call, or `undefined` when the call declares nothing. */\nfunction declarationOf(node: TSESTree.CallExpression): Declaration | undefined {\n const name = calleeName(node);\n\n if (name === undefined || !DECLARATIONS.has(name)) return undefined;\n\n const id = idOfArgument(node);\n\n return id === undefined ? undefined : { id, name };\n}\n\nexport { declarationOf };\n"],"names":["DECLARATIONS","idOfArgument","node","first","AST_NODE_TYPES","declarationOf","name","calleeName","id"],"mappings":"gGAUA,MAAMA,EAAe,IAAI,IAAI,CAC3B,oBACA,kBACA,gBACA,qBACA,cACA,aACA,aACA,iBACA,aACA,kBACD,CAAA,EAOD,SAASC,EAAaC,EAA6B,CACjD,KAAM,CAACC,CAAK,EAAID,EAAK,UAErB,GAAI,EAAAC,IAAU,QAAaA,EAAM,OAASC,EAAe,eAEzD,OAAOD,CACT,CAGA,SAASE,EAAcH,EAA6B,CAClD,MAAMI,EAAOC,EAAWL,CAAI,EAE5B,GAAII,IAAS,QAAa,CAACN,EAAa,IAAIM,CAAI,EAAG,OAEnD,MAAME,EAAKP,EAAaC,CAAI,EAE5B,OAAOM,IAAO,OAAY,OAAY,CAAE,GAAAA,EAAI,KAAAF,CAAI,CAClD"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{defineFeaturePropertyOrder as p}from"./rules/define-feature-property-order.js";import{idNaming as s}from"./rules/id-naming.js";import{layerPlacement as i}from"./rules/layer-placement.js";import{noInternalImports as l}from"./rules/no-internal-imports.js";import{noSnapshotReadInRender as m}from"./rules/no-snapshot-read-in-render.js";import{noSubscribeOutsideModels as a}from"./rules/no-subscribe-outside-models.js";import{preferModelSelection as d}from"./rules/prefer-model-selection.js";import{requireDeclaredModels as c}from"./rules/require-declared-models.js";import{requireLiteralId as u}from"./rules/require-literal-id.js";import{whenPredicate as f}from"./rules/when-predicate.js";const g=["integration","models","ui"],b=["**/*.ts","**/*.tsx"],h=["**/__tests__/**","**/*.spec.ts","**/*.spec.tsx"],y={meta:{name:"@opetope/lint",version:"0.7.0"},rules:{"define-feature-property-order":p,"id-naming":s,"layer-placement":i,"no-internal-imports":l,"no-snapshot-read-in-render":m,"no-subscribe-outside-models":a,"prefer-model-selection":d,"require-declared-models":c,"require-literal-id":u,"when-predicate":f}},r=y,S={name:"opetope/recommended",plugins:{opetope:r},rules:{"opetope/define-feature-property-order":"error","opetope/id-naming":"error","opetope/layer-placement":"off","opetope/no-internal-imports":"error","opetope/no-snapshot-read-in-render":"error","opetope/no-subscribe-outside-models":"off","opetope/prefer-model-selection":"off","opetope/require-declared-models":"error","opetope/require-literal-id":"error","opetope/when-predicate":"error"}},I=e=>g.flatMap(o=>{const t=e[o]??[];return t.length===0?[]:[{files:[...t],name:`opetope/layers-${o}`,plugins:{opetope:r},rules:{"opetope/layer-placement":["error",{layer:o}]}}]}),q=({models:e=[],tests:o=h})=>e.length===0?[]:[{files:b,name:"opetope/layers-subscriptions",plugins:{opetope:r},rules:{"opetope/no-subscribe-outside-models":"error"}},{files:[...e,...o],name:"opetope/layers-subscriptions-allowed",rules:{"opetope/no-subscribe-outside-models":"off"}}],_=e=>[...I(e),...q(e)],j=({allow:e=[],files:o=["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"]}={})=>[{files:[...o],name:"opetope/internal-imports",plugins:{opetope:r},rules:{"opetope/no-internal-imports":"error"}},...e.length===0?[]:[{files:[...e],name:"opetope/internal-imports-allowed",rules:{"opetope/no-internal-imports":"off"}}]],x=Object.assign(r,{configs:{internalImports:j,layers:_,recommended:S}}),n=x;export{n as default,n as opetopeLint};
1
+ import{defineFeaturePropertyOrder as p}from"./rules/define-feature-property-order.js";import{idNaming as s}from"./rules/id-naming.js";import{layerPlacement as i}from"./rules/layer-placement.js";import{noInternalImports as l}from"./rules/no-internal-imports.js";import{noSnapshotReadInRender as m}from"./rules/no-snapshot-read-in-render.js";import{noSubscribeOutsideModels as a}from"./rules/no-subscribe-outside-models.js";import{preferModelSelection as d}from"./rules/prefer-model-selection.js";import{requireDeclaredModels as c}from"./rules/require-declared-models.js";import{requireLiteralId as u}from"./rules/require-literal-id.js";import{whenPredicate as f}from"./rules/when-predicate.js";const g=["integration","models","ui"],b=["**/*.ts","**/*.tsx"],h=["**/__tests__/**","**/*.spec.ts","**/*.spec.tsx"],y={meta:{name:"@opetope/lint",version:"0.8.1"},rules:{"define-feature-property-order":p,"id-naming":s,"layer-placement":i,"no-internal-imports":l,"no-snapshot-read-in-render":m,"no-subscribe-outside-models":a,"prefer-model-selection":d,"require-declared-models":c,"require-literal-id":u,"when-predicate":f}},r=y,S={name:"opetope/recommended",plugins:{opetope:r},rules:{"opetope/define-feature-property-order":"error","opetope/id-naming":"error","opetope/layer-placement":"off","opetope/no-internal-imports":"error","opetope/no-snapshot-read-in-render":"error","opetope/no-subscribe-outside-models":"off","opetope/prefer-model-selection":"off","opetope/require-declared-models":"error","opetope/require-literal-id":"error","opetope/when-predicate":"error"}},I=e=>g.flatMap(o=>{const t=e[o]??[];return t.length===0?[]:[{files:[...t],name:`opetope/layers-${o}`,plugins:{opetope:r},rules:{"opetope/layer-placement":["error",{layer:o}]}}]}),q=({models:e=[],tests:o=h})=>e.length===0?[]:[{files:b,name:"opetope/layers-subscriptions",plugins:{opetope:r},rules:{"opetope/no-subscribe-outside-models":"error"}},{files:[...e,...o],name:"opetope/layers-subscriptions-allowed",rules:{"opetope/no-subscribe-outside-models":"off"}}],_=e=>[...I(e),...q(e)],j=({allow:e=[],files:o=["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"]}={})=>[{files:[...o],name:"opetope/internal-imports",plugins:{opetope:r},rules:{"opetope/no-internal-imports":"error"}},...e.length===0?[]:[{files:[...e],name:"opetope/internal-imports-allowed",rules:{"opetope/no-internal-imports":"off"}}]],x=Object.assign(r,{configs:{internalImports:j,layers:_,recommended:S}}),n=x;export{n as default,n as opetopeLint};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\n\nimport { defineFeaturePropertyOrder } from './rules/define-feature-property-order';\nimport { idNaming } from './rules/id-naming';\nimport { layerPlacement } from './rules/layer-placement';\nimport { noInternalImports } from './rules/no-internal-imports';\nimport { noSnapshotReadInRender } from './rules/no-snapshot-read-in-render';\nimport { noSubscribeOutsideModels } from './rules/no-subscribe-outside-models';\nimport { preferModelSelection } from './rules/prefer-model-selection';\nimport { requireDeclaredModels } from './rules/require-declared-models';\nimport { requireLiteralId } from './rules/require-literal-id';\nimport { whenPredicate } from './rules/when-predicate';\n\n/**\n * The layers a host names for itself, as globs of the files that hold each one, with the tests that are allowed\n * to reach past a layer. `tests` defaults to the usual two shapes of a test path.\n */\ntype LayerOptions = {\n readonly integration?: readonly string[];\n readonly models?: readonly string[];\n readonly tests?: readonly string[];\n readonly ui?: readonly string[];\n};\n\ntype InternalImportOptions = {\n readonly allow?: readonly string[];\n readonly files?: readonly string[];\n};\n\nconst LAYERS = ['integration', 'models', 'ui'] as const;\nconst SOURCE_FILES = ['**/*.ts', '**/*.tsx'];\nconst TEST_FILES = ['**/__tests__/**', '**/*.spec.ts', '**/*.spec.tsx'];\n\nconst plugin = {\n meta: { name: '@opetope/lint', version: '0.7.0' },\n rules: {\n 'define-feature-property-order': defineFeaturePropertyOrder,\n 'id-naming': idNaming,\n 'layer-placement': layerPlacement,\n 'no-internal-imports': noInternalImports,\n 'no-snapshot-read-in-render': noSnapshotReadInRender,\n 'no-subscribe-outside-models': noSubscribeOutsideModels,\n 'prefer-model-selection': preferModelSelection,\n 'require-declared-models': requireDeclaredModels,\n 'require-literal-id': requireLiteralId,\n 'when-predicate': whenPredicate,\n },\n};\n\n/**\n * The laws every Opetope author writes against, with no assumption about where a host keeps its files. A rule that\n * needs such an assumption is off here and arrives through `layers`. The namespace is `opetope`, so a rule reads\n * `opetope/when-predicate` wherever a host names it.\n */\n// Rules use the parser's typed AST internally. The public bridge exposes the\n// supported ESLint rule API; package smoke tests execute it under both linters.\ntype NativeRule = NonNullable<ESLint.Plugin['rules']>[string];\ntype NativeRules = {\n [Name in keyof typeof plugin.rules]: Omit<(typeof plugin.rules)[Name], 'create'> & NativeRule;\n};\nconst nativePlugin = plugin as unknown as { meta: typeof plugin.meta; rules: NativeRules };\n\nconst recommended = {\n name: 'opetope/recommended',\n plugins: { opetope: nativePlugin },\n rules: {\n 'opetope/define-feature-property-order': 'error',\n 'opetope/id-naming': 'error',\n 'opetope/layer-placement': 'off',\n 'opetope/no-internal-imports': 'error',\n 'opetope/no-snapshot-read-in-render': 'error',\n 'opetope/no-subscribe-outside-models': 'off',\n 'opetope/prefer-model-selection': 'off',\n 'opetope/require-declared-models': 'error',\n 'opetope/require-literal-id': 'error',\n 'opetope/when-predicate': 'error',\n },\n} satisfies Linter.Config;\n\n/**\n * Placement is a convention of the project that names its layers, not a law of the framework: a layer without a\n * glob is a layer this host does not have, and it produces no configuration.\n */\nconst placement = (options: LayerOptions): readonly Linter.Config[] =>\n LAYERS.flatMap(layer => {\n const files = options[layer] ?? [];\n\n if (files.length === 0) return [];\n\n return [\n {\n files: [...files],\n name: `opetope/layers-${layer}`,\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/layer-placement': ['error', { layer }] },\n } satisfies Linter.Config,\n ];\n });\n\n/**\n * A subscription is written where the state lives, so this pair arrives only with the model layer that owns it. The\n * restriction covers every source file and steps aside in that layer and in tests: a file no layer claims is\n * covered rather than forgotten.\n */\nconst subscriptions = ({ models = [], tests = TEST_FILES }: LayerOptions): readonly Linter.Config[] =>\n models.length === 0\n ? []\n : [\n {\n files: SOURCE_FILES,\n name: 'opetope/layers-subscriptions',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-subscribe-outside-models': 'error' },\n },\n {\n files: [...models, ...tests],\n name: 'opetope/layers-subscriptions-allowed',\n rules: { 'opetope/no-subscribe-outside-models': 'off' },\n },\n ];\n\nconst layers = (options: LayerOptions): readonly Linter.Config[] => [...placement(options), ...subscriptions(options)];\n\n/**\n * Only library implementation and explicitly named host integration can cross the internal ABI. A dedicated rule\n * leaves the project's `no-restricted-imports` untouched and also covers literal dynamic imports in JS tests.\n * Allowed files explicitly override `recommended`, which enables this rule as well.\n */\nconst internalImports = ({\n allow = [],\n files = ['**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'],\n}: InternalImportOptions = {}): readonly Linter.Config[] => [\n {\n files: [...files],\n name: 'opetope/internal-imports',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-internal-imports': 'error' },\n },\n ...(allow.length === 0\n ? []\n : [\n {\n files: [...allow],\n name: 'opetope/internal-imports-allowed',\n rules: { 'opetope/no-internal-imports': 'off' as const },\n },\n ]),\n];\n\nconst configuredPlugin = Object.assign(nativePlugin, {\n configs: { internalImports, layers, recommended },\n});\n\n// ESLint consumes the rule map. Config factories are additional plugin helpers;\n// expose both contracts without requiring casts at native flat-config call sites.\n// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- Required by native ESLint config typetests.\nconst opetopeLint = configuredPlugin as unknown as typeof configuredPlugin & Pick<ESLint.Plugin, 'configs'>;\n\n/*\n * Two shapes of one object. The default is what a tool reads when it loads a plugin by path — Oxlint's `jsPlugins`\n * takes `(await import(path)).default` — and what an ESLint flat config imports by convention; the named export is\n * for a config that prefers to say which binding it takes.\n */\n// oxlint-disable-next-line import/no-default-export\n// eslint-disable-next-line import-x/no-default-export\nexport default opetopeLint;\nexport { opetopeLint };\n"],"names":["LAYERS","SOURCE_FILES","TEST_FILES","plugin","defineFeaturePropertyOrder","idNaming","layerPlacement","noInternalImports","noSnapshotReadInRender","noSubscribeOutsideModels","preferModelSelection","requireDeclaredModels","requireLiteralId","whenPredicate","nativePlugin","recommended","placement","options","layer","files","subscriptions","models","tests","layers","internalImports","allow","configuredPlugin","opetopeLint"],"mappings":"qrBA6BA,MAAMA,EAAS,CAAC,cAAe,SAAU,IAAI,EACvCC,EAAe,CAAC,UAAW,UAAU,EACrCC,EAAa,CAAC,kBAAmB,eAAgB,eAAe,EAEhEC,EAAS,CACb,KAAM,CAAE,KAAM,gBAAiB,QAAS,OAAO,EAC/C,MAAO,CACL,gCAAiCC,EACjC,YAAaC,EACb,kBAAmBC,EACnB,sBAAuBC,EACvB,6BAA8BC,EAC9B,8BAA+BC,EAC/B,yBAA0BC,EAC1B,0BAA2BC,EAC3B,qBAAsBC,EACtB,iBAAkBC,CACnB,GAcGC,EAAeX,EAEfY,EAAc,CAClB,KAAM,sBACN,QAAS,CAAE,QAASD,CAAY,EAChC,MAAO,CACL,wCAAyC,QACzC,oBAAqB,QACrB,0BAA2B,MAC3B,8BAA+B,QAC/B,qCAAsC,QACtC,sCAAuC,MACvC,iCAAkC,MAClC,kCAAmC,QACnC,6BAA8B,QAC9B,yBAA0B,OAC3B,GAOGE,EAAaC,GACjBjB,EAAO,QAAQkB,GAAQ,CACrB,MAAMC,EAAQF,EAAQC,CAAK,GAAK,CAAA,EAEhC,OAAIC,EAAM,SAAW,EAAU,CAAA,EAExB,CACL,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,kBAAkBD,CAAK,GAC7B,QAAS,CAAE,QAASJ,CAAY,EAChC,MAAO,CAAE,0BAA2B,CAAC,QAAS,CAAE,MAAAI,CAAK,CAAE,CAAC,CACjC,EAE7B,CAAC,EAOGE,EAAgB,CAAC,CAAE,OAAAC,EAAS,CAAA,EAAI,MAAAC,EAAQpB,CAAU,IACtDmB,EAAO,SAAW,EACd,CAAA,EACA,CACE,CACE,MAAOpB,EACP,KAAM,+BACN,QAAS,CAAE,QAASa,CAAY,EAChC,MAAO,CAAE,sCAAuC,OAAO,CACxD,EACD,CACE,MAAO,CAAC,GAAGO,EAAQ,GAAGC,CAAK,EAC3B,KAAM,uCACN,MAAO,CAAE,sCAAuC,KAAK,CACtD,GAGHC,EAAUN,GAAoD,CAAC,GAAGD,EAAUC,CAAO,EAAG,GAAGG,EAAcH,CAAO,CAAC,EAO/GO,EAAkB,CAAC,CACvB,MAAAC,EAAQ,CAAA,EACR,MAAAN,EAAQ,CAAC,sCAAsC,CAAC,EACvB,KAAiC,CAC1D,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,2BACN,QAAS,CAAE,QAASL,CAAY,EAChC,MAAO,CAAE,8BAA+B,OAAO,CAChD,EACD,GAAIW,EAAM,SAAW,EACjB,CAAA,EACA,CACE,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,mCACN,MAAO,CAAE,8BAA+B,KAAc,CACvD,IAIHC,EAAmB,OAAO,OAAOZ,EAAc,CACnD,QAAS,CAAE,gBAAAU,EAAiB,OAAAD,EAAQ,YAAAR,CAAW,CAChD,CAAA,EAKKY,EAAcD"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\n\nimport { defineFeaturePropertyOrder } from './rules/define-feature-property-order';\nimport { idNaming } from './rules/id-naming';\nimport { layerPlacement } from './rules/layer-placement';\nimport { noInternalImports } from './rules/no-internal-imports';\nimport { noSnapshotReadInRender } from './rules/no-snapshot-read-in-render';\nimport { noSubscribeOutsideModels } from './rules/no-subscribe-outside-models';\nimport { preferModelSelection } from './rules/prefer-model-selection';\nimport { requireDeclaredModels } from './rules/require-declared-models';\nimport { requireLiteralId } from './rules/require-literal-id';\nimport { whenPredicate } from './rules/when-predicate';\n\n/**\n * The layers a host names for itself, as globs of the files that hold each one, with the tests that are allowed\n * to reach past a layer. `tests` defaults to the usual two shapes of a test path.\n */\ntype LayerOptions = {\n readonly integration?: readonly string[];\n readonly models?: readonly string[];\n readonly tests?: readonly string[];\n readonly ui?: readonly string[];\n};\n\ntype InternalImportOptions = {\n readonly allow?: readonly string[];\n readonly files?: readonly string[];\n};\n\nconst LAYERS = ['integration', 'models', 'ui'] as const;\nconst SOURCE_FILES = ['**/*.ts', '**/*.tsx'];\nconst TEST_FILES = ['**/__tests__/**', '**/*.spec.ts', '**/*.spec.tsx'];\n\nconst plugin = {\n meta: { name: '@opetope/lint', version: '0.8.1' },\n rules: {\n 'define-feature-property-order': defineFeaturePropertyOrder,\n 'id-naming': idNaming,\n 'layer-placement': layerPlacement,\n 'no-internal-imports': noInternalImports,\n 'no-snapshot-read-in-render': noSnapshotReadInRender,\n 'no-subscribe-outside-models': noSubscribeOutsideModels,\n 'prefer-model-selection': preferModelSelection,\n 'require-declared-models': requireDeclaredModels,\n 'require-literal-id': requireLiteralId,\n 'when-predicate': whenPredicate,\n },\n};\n\n/**\n * The laws every Opetope author writes against, with no assumption about where a host keeps its files. A rule that\n * needs such an assumption is off here and arrives through `layers`. The namespace is `opetope`, so a rule reads\n * `opetope/when-predicate` wherever a host names it.\n */\n// Rules use the parser's typed AST internally. The public bridge exposes the\n// supported ESLint rule API; package smoke tests execute it under both linters.\ntype NativeRule = NonNullable<ESLint.Plugin['rules']>[string];\ntype NativeRules = {\n [Name in keyof typeof plugin.rules]: Omit<(typeof plugin.rules)[Name], 'create'> & NativeRule;\n};\nconst nativePlugin = plugin as unknown as { meta: typeof plugin.meta; rules: NativeRules };\n\nconst recommended = {\n name: 'opetope/recommended',\n plugins: { opetope: nativePlugin },\n rules: {\n 'opetope/define-feature-property-order': 'error',\n 'opetope/id-naming': 'error',\n 'opetope/layer-placement': 'off',\n 'opetope/no-internal-imports': 'error',\n 'opetope/no-snapshot-read-in-render': 'error',\n 'opetope/no-subscribe-outside-models': 'off',\n 'opetope/prefer-model-selection': 'off',\n 'opetope/require-declared-models': 'error',\n 'opetope/require-literal-id': 'error',\n 'opetope/when-predicate': 'error',\n },\n} satisfies Linter.Config;\n\n/**\n * Placement is a convention of the project that names its layers, not a law of the framework: a layer without a\n * glob is a layer this host does not have, and it produces no configuration.\n */\nconst placement = (options: LayerOptions): readonly Linter.Config[] =>\n LAYERS.flatMap(layer => {\n const files = options[layer] ?? [];\n\n if (files.length === 0) return [];\n\n return [\n {\n files: [...files],\n name: `opetope/layers-${layer}`,\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/layer-placement': ['error', { layer }] },\n } satisfies Linter.Config,\n ];\n });\n\n/**\n * A subscription is written where the state lives, so this pair arrives only with the model layer that owns it. The\n * restriction covers every source file and steps aside in that layer and in tests: a file no layer claims is\n * covered rather than forgotten.\n */\nconst subscriptions = ({ models = [], tests = TEST_FILES }: LayerOptions): readonly Linter.Config[] =>\n models.length === 0\n ? []\n : [\n {\n files: SOURCE_FILES,\n name: 'opetope/layers-subscriptions',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-subscribe-outside-models': 'error' },\n },\n {\n files: [...models, ...tests],\n name: 'opetope/layers-subscriptions-allowed',\n rules: { 'opetope/no-subscribe-outside-models': 'off' },\n },\n ];\n\nconst layers = (options: LayerOptions): readonly Linter.Config[] => [...placement(options), ...subscriptions(options)];\n\n/**\n * Only library implementation and explicitly named host integration can cross the internal ABI. A dedicated rule\n * leaves the project's `no-restricted-imports` untouched and also covers literal dynamic imports in JS tests.\n * Allowed files explicitly override `recommended`, which enables this rule as well.\n */\nconst internalImports = ({\n allow = [],\n files = ['**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'],\n}: InternalImportOptions = {}): readonly Linter.Config[] => [\n {\n files: [...files],\n name: 'opetope/internal-imports',\n plugins: { opetope: nativePlugin },\n rules: { 'opetope/no-internal-imports': 'error' },\n },\n ...(allow.length === 0\n ? []\n : [\n {\n files: [...allow],\n name: 'opetope/internal-imports-allowed',\n rules: { 'opetope/no-internal-imports': 'off' as const },\n },\n ]),\n];\n\nconst configuredPlugin = Object.assign(nativePlugin, {\n configs: { internalImports, layers, recommended },\n});\n\n// ESLint consumes the rule map. Config factories are additional plugin helpers;\n// expose both contracts without requiring casts at native flat-config call sites.\n// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- Required by native ESLint config typetests.\nconst opetopeLint = configuredPlugin as unknown as typeof configuredPlugin & Pick<ESLint.Plugin, 'configs'>;\n\n/*\n * Two shapes of one object. The default is what a tool reads when it loads a plugin by path — Oxlint's `jsPlugins`\n * takes `(await import(path)).default` — and what an ESLint flat config imports by convention; the named export is\n * for a config that prefers to say which binding it takes.\n */\n// oxlint-disable-next-line import/no-default-export\n// eslint-disable-next-line import-x/no-default-export\nexport default opetopeLint;\nexport { opetopeLint };\n"],"names":["LAYERS","SOURCE_FILES","TEST_FILES","plugin","defineFeaturePropertyOrder","idNaming","layerPlacement","noInternalImports","noSnapshotReadInRender","noSubscribeOutsideModels","preferModelSelection","requireDeclaredModels","requireLiteralId","whenPredicate","nativePlugin","recommended","placement","options","layer","files","subscriptions","models","tests","layers","internalImports","allow","configuredPlugin","opetopeLint"],"mappings":"qrBA6BA,MAAMA,EAAS,CAAC,cAAe,SAAU,IAAI,EACvCC,EAAe,CAAC,UAAW,UAAU,EACrCC,EAAa,CAAC,kBAAmB,eAAgB,eAAe,EAEhEC,EAAS,CACb,KAAM,CAAE,KAAM,gBAAiB,QAAS,OAAO,EAC/C,MAAO,CACL,gCAAiCC,EACjC,YAAaC,EACb,kBAAmBC,EACnB,sBAAuBC,EACvB,6BAA8BC,EAC9B,8BAA+BC,EAC/B,yBAA0BC,EAC1B,0BAA2BC,EAC3B,qBAAsBC,EACtB,iBAAkBC,CACnB,GAcGC,EAAeX,EAEfY,EAAc,CAClB,KAAM,sBACN,QAAS,CAAE,QAASD,CAAY,EAChC,MAAO,CACL,wCAAyC,QACzC,oBAAqB,QACrB,0BAA2B,MAC3B,8BAA+B,QAC/B,qCAAsC,QACtC,sCAAuC,MACvC,iCAAkC,MAClC,kCAAmC,QACnC,6BAA8B,QAC9B,yBAA0B,OAC3B,GAOGE,EAAaC,GACjBjB,EAAO,QAAQkB,GAAQ,CACrB,MAAMC,EAAQF,EAAQC,CAAK,GAAK,CAAA,EAEhC,OAAIC,EAAM,SAAW,EAAU,CAAA,EAExB,CACL,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,kBAAkBD,CAAK,GAC7B,QAAS,CAAE,QAASJ,CAAY,EAChC,MAAO,CAAE,0BAA2B,CAAC,QAAS,CAAE,MAAAI,CAAK,CAAE,CAAC,CACjC,EAE7B,CAAC,EAOGE,EAAgB,CAAC,CAAE,OAAAC,EAAS,CAAA,EAAI,MAAAC,EAAQpB,CAAU,IACtDmB,EAAO,SAAW,EACd,CAAA,EACA,CACE,CACE,MAAOpB,EACP,KAAM,+BACN,QAAS,CAAE,QAASa,CAAY,EAChC,MAAO,CAAE,sCAAuC,OAAO,CACxD,EACD,CACE,MAAO,CAAC,GAAGO,EAAQ,GAAGC,CAAK,EAC3B,KAAM,uCACN,MAAO,CAAE,sCAAuC,KAAK,CACtD,GAGHC,EAAUN,GAAoD,CAAC,GAAGD,EAAUC,CAAO,EAAG,GAAGG,EAAcH,CAAO,CAAC,EAO/GO,EAAkB,CAAC,CACvB,MAAAC,EAAQ,CAAA,EACR,MAAAN,EAAQ,CAAC,sCAAsC,CAAC,EACvB,KAAiC,CAC1D,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,2BACN,QAAS,CAAE,QAASL,CAAY,EAChC,MAAO,CAAE,8BAA+B,OAAO,CAChD,EACD,GAAIW,EAAM,SAAW,EACjB,CAAA,EACA,CACE,CACE,MAAO,CAAC,GAAGA,CAAK,EAChB,KAAM,mCACN,MAAO,CAAE,8BAA+B,KAAc,CACvD,IAIHC,EAAmB,OAAO,OAAOZ,EAAc,CACnD,QAAS,CAAE,gBAAAU,EAAiB,OAAAD,EAAQ,YAAAR,CAAW,CAChD,CAAA,EAKKY,EAAcD"}
@@ -1,2 +1,2 @@
1
- import{AST_NODE_TYPES as p}from"@typescript-eslint/utils";import{calleeName as f,propertyName as a}from"../ast.js";import{createRule as u}from"../rule.js";const c=["id","imports","requires","own","exports","provides","when","body"];function d(o){const n=a(o);return n===void 0?-1:c.indexOf(n)}function m(o){const n=[];for(const e of o){const r=d(e);if(r<0)return;n.push(r)}return n}function l(o){let n=-1;for(const[e,r]of o.entries()){if(r<n)return e;n=r}return-1}function x(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*g(o,n,e){const r=[...e].sort((s,t)=>d(s)-d(t));for(const[s,t]of e.entries()){const i=r[s];i!==void 0&&i!==t&&(yield o.replaceText(t,n.getText(i)))}}const y=u({create(o){const n=e=>{const r=m(e.properties);if(r===void 0)return;const s=l(r),t=e.properties[s];t!==void 0&&o.report({fix:x(o.sourceCode,e)?null:i=>[...g(i,o.sourceCode,e.properties)],messageId:"sectionOrder",node:t})};return{CallExpression(e){const[r]=e.arguments;f(e)!=="defineFeature"||r?.type!==p.ObjectExpression||n(r)}}},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: ${c.join(", ")} (spec \xA72.1, D186).`},schema:[],type:"layout"},name:"define-feature-property-order"});export{y as defineFeaturePropertyOrder};
1
+ import{AST_NODE_TYPES as f}from"@typescript-eslint/utils";import{calleeName as p,propertyName as a}from"../ast.js";import{createRule as u}from"../rule.js";const c=["imports","requires","own","exports","provides","when","body"];function d(o){const n=a(o);return n===void 0?-1:c.indexOf(n)}function m(o){const n=[];for(const e of o){const r=d(e);if(r<0)return;n.push(r)}return n}function l(o){let n=-1;for(const[e,r]of o.entries()){if(r<n)return e;n=r}return-1}function x(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*g(o,n,e){const r=[...e].sort((s,t)=>d(s)-d(t));for(const[s,t]of e.entries()){const i=r[s];i!==void 0&&i!==t&&(yield o.replaceText(t,n.getText(i)))}}const y=u({create(o){const n=e=>{const r=m(e.properties);if(r===void 0)return;const s=l(r),t=e.properties[s];t!==void 0&&o.report({fix:x(o.sourceCode,e)?null:i=>[...g(i,o.sourceCode,e.properties)],messageId:"sectionOrder",node:t})};return{CallExpression(e){const[,r]=e.arguments;p(e)!=="defineFeature"||r?.type!==f.ObjectExpression||n(r)}}},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: ${c.join(", ")} (spec \xA72.1, D186).`},schema:[],type:"layout"},name:"define-feature-property-order"});export{y 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: identity, the edges, the stages, then the lifetime and the loader that\n * replaces the last three stages. The specification names the sections; the order is the one a key sorter can also\n * be configured to hold, so a host runs one convention instead of two (D186).\n */\nconst SECTIONS = ['id', '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 rule can place. */\nfunction sectionOrder(properties: Properties): readonly number[] | undefined {\n const order: number[] = [];\n\n for (const property of properties) {\n const index = sectionIndex(property);\n\n if (index < 0) return undefined;\n\n order.push(index);\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): void => {\n const order = sectionOrder(object.properties);\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 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 const [options] = node.arguments;\n\n if (calleeName(node) !== 'defineFeature' || options?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n check(options);\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: ${SECTIONS.join(', ')} (spec §2.1, D186).`,\n },\n schema: [],\n type: 'layout',\n },\n name: 'define-feature-property-order',\n});\n\nexport { defineFeaturePropertyOrder };\n"],"names":["SECTIONS","sectionIndex","property","name","propertyName","sectionOrder","properties","order","index","firstMisplaced","previous","value","hasSeparatingComment","sourceCode","object","comment","reorder","fixer","sorted","left","right","replacement","defineFeaturePropertyOrder","createRule","context","check","misplaced","node","options","calleeName","AST_NODE_TYPES"],"mappings":"2JAcA,MAAMA,EAAW,CAAC,KAAM,UAAW,WAAY,MAAO,UAAW,WAAY,OAAQ,MAAM,EAE3F,SAASC,EAAaC,EAAuC,CAC3D,MAAMC,EAAOC,EAAaF,CAAQ,EAElC,OAAOC,IAAS,OAAY,GAAKH,EAAS,QAAQG,CAAI,CACxD,CAGA,SAASE,EAAaC,EAAsB,CAC1C,MAAMC,EAAkB,CAAA,EAExB,UAAWL,KAAYI,EAAY,CACjC,MAAME,EAAQP,EAAaC,CAAQ,EAEnC,GAAIM,EAAQ,EAAG,OAEfD,EAAM,KAAKC,CAAK,CAClB,CAEA,OAAOD,CACT,CAEA,SAASE,EAAeF,EAAwB,CAC9C,IAAIG,EAAW,GAEf,SAAW,CAACF,EAAOG,CAAK,IAAKJ,EAAM,QAAO,EAAI,CAC5C,GAAII,EAAQD,EAAU,OAAOF,EAE7BE,EAAWC,CACb,CAEA,MAAO,EACT,CAMA,SAASC,EAAqBC,EAAiCC,EAAiC,CAC9F,OAAOD,EACJ,kBAAkBC,CAAM,EACxB,KACCC,GACE,CAACD,EAAO,WAAW,KACjBZ,GAAYA,EAAS,MAAM,CAAC,GAAKa,EAAQ,MAAM,CAAC,GAAKA,EAAQ,MAAM,CAAC,GAAKb,EAAS,MAAM,CAAC,CAAC,CAC3F,CAET,CAEA,SAAUc,EACRC,EACAJ,EACAP,EAAsB,CAEtB,MAAMY,EAAS,CAAC,GAAGZ,CAAU,EAAE,KAAK,CAACa,EAAMC,IAAUnB,EAAakB,CAAI,EAAIlB,EAAamB,CAAK,CAAC,EAE7F,SAAW,CAACZ,EAAON,CAAQ,IAAKI,EAAW,QAAO,EAAI,CACpD,MAAMe,EAAcH,EAAOV,CAAK,EAE5Ba,IAAgB,QAAaA,IAAgBnB,IAC/C,MAAMe,EAAM,YAAYf,EAAUW,EAAW,QAAQQ,CAAW,CAAC,EAErE,CACF,CAEA,MAAMC,EAA6BC,EAA2B,CAC5D,OAAOC,EAAO,CACZ,MAAMC,EAASX,GAA2C,CACxD,MAAMP,EAAQF,EAAaS,EAAO,UAAU,EAE5C,GAAIP,IAAU,OAAW,OAEzB,MAAMmB,EAAYjB,EAAeF,CAAK,EAChCL,EAAWY,EAAO,WAAWY,CAAS,EAExCxB,IAAa,QAEjBsB,EAAQ,OAAO,CACb,IAAKZ,EAAqBY,EAAQ,WAAYV,CAAM,EAChD,KACAG,GAAS,CAAC,GAAGD,EAAQC,EAAOO,EAAQ,WAAYV,EAAO,UAAU,CAAC,EACtE,UAAW,eACX,KAAMZ,CACP,CAAA,CACH,EAEA,MAAO,CACL,eAAeyB,EAAI,CACjB,KAAM,CAACC,CAAO,EAAID,EAAK,UAEnBE,EAAWF,CAAI,IAAM,iBAAmBC,GAAS,OAASE,EAAe,kBAE7EL,EAAMG,CAAO,CACf,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,kFACd,EACD,QAAS,OACT,SAAU,CACR,aAAc,iDAAiD5B,EAAS,KAAK,IAAI,CAAC,wBACnF,EACD,OAAQ,CAAA,EACR,KAAM,QACP,EACD,KAAM,+BACP,CAAA"}
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 rule can place. */\nfunction sectionOrder(properties: Properties): readonly number[] | undefined {\n const order: number[] = [];\n\n for (const property of properties) {\n const index = sectionIndex(property);\n\n if (index < 0) return undefined;\n\n order.push(index);\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): void => {\n const order = sectionOrder(object.properties);\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 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: the id is the first argument, so the sections are the second.\n const [, sections] = node.arguments;\n\n if (calleeName(node) !== 'defineFeature' || sections?.type !== AST_NODE_TYPES.ObjectExpression) return;\n\n check(sections);\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: ${SECTIONS.join(', ')} (spec §2.1, D186).`,\n },\n schema: [],\n type: 'layout',\n },\n name: 'define-feature-property-order',\n});\n\nexport { defineFeaturePropertyOrder };\n"],"names":["SECTIONS","sectionIndex","property","name","propertyName","sectionOrder","properties","order","index","firstMisplaced","previous","value","hasSeparatingComment","sourceCode","object","comment","reorder","fixer","sorted","left","right","replacement","defineFeaturePropertyOrder","createRule","context","check","misplaced","node","sections","calleeName","AST_NODE_TYPES"],"mappings":"2JAeA,MAAMA,EAAW,CAAC,UAAW,WAAY,MAAO,UAAW,WAAY,OAAQ,MAAM,EAErF,SAASC,EAAaC,EAAuC,CAC3D,MAAMC,EAAOC,EAAaF,CAAQ,EAElC,OAAOC,IAAS,OAAY,GAAKH,EAAS,QAAQG,CAAI,CACxD,CAGA,SAASE,EAAaC,EAAsB,CAC1C,MAAMC,EAAkB,CAAA,EAExB,UAAWL,KAAYI,EAAY,CACjC,MAAME,EAAQP,EAAaC,CAAQ,EAEnC,GAAIM,EAAQ,EAAG,OAEfD,EAAM,KAAKC,CAAK,CAClB,CAEA,OAAOD,CACT,CAEA,SAASE,EAAeF,EAAwB,CAC9C,IAAIG,EAAW,GAEf,SAAW,CAACF,EAAOG,CAAK,IAAKJ,EAAM,QAAO,EAAI,CAC5C,GAAII,EAAQD,EAAU,OAAOF,EAE7BE,EAAWC,CACb,CAEA,MAAO,EACT,CAMA,SAASC,EAAqBC,EAAiCC,EAAiC,CAC9F,OAAOD,EACJ,kBAAkBC,CAAM,EACxB,KACCC,GACE,CAACD,EAAO,WAAW,KACjBZ,GAAYA,EAAS,MAAM,CAAC,GAAKa,EAAQ,MAAM,CAAC,GAAKA,EAAQ,MAAM,CAAC,GAAKb,EAAS,MAAM,CAAC,CAAC,CAC3F,CAET,CAEA,SAAUc,EACRC,EACAJ,EACAP,EAAsB,CAEtB,MAAMY,EAAS,CAAC,GAAGZ,CAAU,EAAE,KAAK,CAACa,EAAMC,IAAUnB,EAAakB,CAAI,EAAIlB,EAAamB,CAAK,CAAC,EAE7F,SAAW,CAACZ,EAAON,CAAQ,IAAKI,EAAW,QAAO,EAAI,CACpD,MAAMe,EAAcH,EAAOV,CAAK,EAE5Ba,IAAgB,QAAaA,IAAgBnB,IAC/C,MAAMe,EAAM,YAAYf,EAAUW,EAAW,QAAQQ,CAAW,CAAC,EAErE,CACF,CAEA,MAAMC,EAA6BC,EAA2B,CAC5D,OAAOC,EAAO,CACZ,MAAMC,EAASX,GAA2C,CACxD,MAAMP,EAAQF,EAAaS,EAAO,UAAU,EAE5C,GAAIP,IAAU,OAAW,OAEzB,MAAMmB,EAAYjB,EAAeF,CAAK,EAChCL,EAAWY,EAAO,WAAWY,CAAS,EAExCxB,IAAa,QAEjBsB,EAAQ,OAAO,CACb,IAAKZ,EAAqBY,EAAQ,WAAYV,CAAM,EAChD,KACAG,GAAS,CAAC,GAAGD,EAAQC,EAAOO,EAAQ,WAAYV,EAAO,UAAU,CAAC,EACtE,UAAW,eACX,KAAMZ,CACP,CAAA,CACH,EAEA,MAAO,CACL,eAAeyB,EAAI,CAEjB,KAAM,CAAA,CAAGC,CAAQ,EAAID,EAAK,UAEtBE,EAAWF,CAAI,IAAM,iBAAmBC,GAAU,OAASE,EAAe,kBAE9EL,EAAMG,CAAQ,CAChB,EAEJ,EACA,KAAM,CACJ,KAAM,CACJ,YAAa,kFACd,EACD,QAAS,OACT,SAAU,CACR,aAAc,iDAAiD5B,EAAS,KAAK,IAAI,CAAC,wBACnF,EACD,OAAQ,CAAA,EACR,KAAM,QACP,EACD,KAAM,+BACP,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opetope/lint",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },