@ilha/router 0.11.0 → 0.11.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 (2) hide show
  1. package/oxlint.cjs +69 -25
  2. package/package.json +3 -3
package/oxlint.cjs CHANGED
@@ -1,6 +1,5 @@
1
- const PRIMITIVES = new Set(["atom", "when", "watch", "wait"]);
2
- // when/watch/wait return Instructions — they are only consumable by a generator frame.
3
- const INSTRUCTIONS = new Set(["when", "watch", "wait"]);
1
+ const PRIMITIVES = new Set(["atom", "watch", "when"]);
2
+ const INSTRUCTIONS = new Set(["when"]);
4
3
  const CONDITIONAL = new Set([
5
4
  "IfStatement",
6
5
  "ForStatement",
@@ -38,8 +37,48 @@ function ancestorsOf(context, node) {
38
37
  return [];
39
38
  }
40
39
 
41
- function calleeName(node) {
42
- return node.type === "Identifier" ? node.name : null;
40
+ function blockBody(node) {
41
+ if (node.type === "BlockStatement") return node.body;
42
+ if (
43
+ node.type === "FunctionDeclaration" ||
44
+ node.type === "FunctionExpression" ||
45
+ node.type === "ArrowFunctionExpression"
46
+ ) {
47
+ const body = node.body;
48
+ if (body?.type === "BlockStatement") return body.body;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function bindingInitType(name, ancestors) {
54
+ for (let i = ancestors.length - 1; i >= 0; i--) {
55
+ const a = ancestors[i];
56
+ if (a.type === "VariableDeclarator" && a.id?.type === "Identifier" && a.id.name === name) {
57
+ return a.init?.type ?? "none";
58
+ }
59
+ if (a.type === "FunctionDeclaration" && a.id?.name === name) {
60
+ return "FunctionDeclaration";
61
+ }
62
+ const stmts = blockBody(a);
63
+ if (stmts) {
64
+ for (const stmt of stmts) {
65
+ if (stmt.type !== "VariableDeclaration") continue;
66
+ for (const decl of stmt.declarations) {
67
+ if (decl.id?.type === "Identifier" && decl.id.name === name) {
68
+ return decl.init?.type ?? "none";
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+
77
+ function isFunctionBinding(name, ancestors, moduleFns) {
78
+ const local = bindingInitType(name, ancestors);
79
+ if (local === "FunctionDeclaration") return true;
80
+ if (local !== null) return FN_TYPES.has(local);
81
+ return moduleFns.has(name);
43
82
  }
44
83
 
45
84
  function enclosingFunctions(ancestors) {
@@ -49,7 +88,7 @@ function enclosingFunctions(ancestors) {
49
88
  const noConditionalPrimitive = {
50
89
  meta: {
51
90
  type: "problem",
52
- docs: { description: "Disallow atom/when/watch/wait inside conditionals or loops" },
91
+ docs: { description: "Disallow atom/watch/when inside conditionals or loops" },
53
92
  messages: {
54
93
  conditional:
55
94
  "Do not call {{name}}() inside a condition or loop. Primitive registration order must be stable — put the branch inside the primitive or the view.",
@@ -83,7 +122,7 @@ const noPrimitiveOutsideComponent = {
83
122
  meta: {
84
123
  type: "problem",
85
124
  docs: {
86
- description: "Call atom/when/watch/wait inside a component, never at module top level",
125
+ description: "Call atom/watch/when inside a component, never at module top level",
87
126
  },
88
127
  messages: {
89
128
  outside:
@@ -112,7 +151,7 @@ const noPrimitiveOutsideComponent = {
112
151
  const noInstructionOutsideGenerator = {
113
152
  meta: {
114
153
  type: "problem",
115
- docs: { description: "Use when/watch/wait only inside a generator, with yield*" },
154
+ docs: { description: "Use when only inside a generator, with yield*" },
116
155
  messages: {
117
156
  notGenerator:
118
157
  "{{name}}() returns an instruction that only a generator frame can run. Make the enclosing component a generator and yield* it.",
@@ -186,18 +225,20 @@ const preferLowercaseEvents = {
186
225
  const functionInAtom = {
187
226
  meta: {
188
227
  type: "problem",
189
- docs: { description: "atom(fn) is a computed initializer — wrap function values" },
228
+ docs: {
229
+ description:
230
+ "atom(fn) and atom(() => …) are invalid — use atom.lazy, Atom.map, or Atom.transform",
231
+ },
190
232
  messages: {
191
- init: "atom({{name}}) treats the function as a computed initializer. Wrap it: atom(() => {{name}}).",
192
- set: "{{setter}}({{name}}) is fine as a value, but computed atoms reject set(). Store a function with a plain wrapper if you really mean it.",
233
+ init: "atom({{name}}) is not valid. Use atom.lazy(() => {{name}}) to store a function value.",
234
+ derived:
235
+ "atom(() => …) is not supported. Use atom(Atom.map(...)) or atom(Atom.transform(...)) for derived values, or atom.lazy() for one-time init.",
193
236
  },
194
237
  schema: [],
195
238
  },
196
239
  create(context) {
197
240
  const b = bindings();
198
241
  const fns = new Set();
199
- const atoms = new Set();
200
- const computed = new Set();
201
242
  return {
202
243
  ImportDeclaration(node) {
203
244
  takeImport(b, node);
@@ -215,23 +256,26 @@ const functionInAtom = {
215
256
  fns.add(node.id.name);
216
257
  }
217
258
  if (!init || primitiveName(init, b) !== "atom") return;
218
- atoms.add(node.id.name);
219
- // atom(() => …) or atom(function…) — computed; flag set() on it.
220
- if (init.arguments[0] && FN_TYPES.has(init.arguments[0].type)) computed.add(node.id.name);
259
+ const arg = init.arguments[0];
260
+ if (arg && FN_TYPES.has(arg.type)) {
261
+ context.report({ node: arg, messageId: "derived" });
262
+ }
263
+ if (
264
+ arg?.type === "Identifier" &&
265
+ isFunctionBinding(arg.name, ancestorsOf(context, arg), fns)
266
+ ) {
267
+ context.report({ node: arg, messageId: "init", data: { name: arg.name } });
268
+ }
221
269
  },
222
270
  CallExpression(node) {
223
271
  if (node.arguments.length === 0) return;
224
272
  const arg = node.arguments[0];
225
- if (arg.type !== "Identifier" || !fns.has(arg.name)) return;
226
- if (primitiveName(node, b) === "atom") {
273
+ if (arg.type !== "Identifier") return;
274
+ if (
275
+ primitiveName(node, b) === "atom" &&
276
+ isFunctionBinding(arg.name, ancestorsOf(context, arg), fns)
277
+ ) {
227
278
  context.report({ node: arg, messageId: "init", data: { name: arg.name } });
228
- return;
229
- }
230
- const setter = calleeName(node.callee);
231
- // set(fn) on a computed atom never makes sense; on a writable atom it
232
- // is the classic "I meant to store this function" mistake.
233
- if (setter && atoms.has(setter) && computed.has(setter)) {
234
- context.report({ node: arg, messageId: "set", data: { setter, name: arg.name } });
235
279
  }
236
280
  },
237
281
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilha/router",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "A tiny SPA router for Ilha",
5
5
  "keywords": [
6
6
  "frontend",
@@ -73,10 +73,10 @@
73
73
  "unplugin": "3.3.0"
74
74
  },
75
75
  "devDependencies": {
76
- "ilha": "0.14.0",
76
+ "ilha": "0.14.1",
77
77
  "vite": "^8.2.2"
78
78
  },
79
79
  "peerDependencies": {
80
- "ilha": ">=0.14.0"
80
+ "ilha": ">=0.14.1"
81
81
  }
82
82
  }