@adea-ai/ui 0.95.2 → 0.95.3

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.
@@ -14,6 +14,9 @@ type RuleModule = {
14
14
  };
15
15
  interface LintContext {
16
16
  options?: unknown[];
17
+ sourceCode?: {
18
+ getScope?: (node: unknown) => LintScope | undefined;
19
+ };
17
20
  report: (descriptor: {
18
21
  node: unknown;
19
22
  messageId?: string;
@@ -21,6 +24,10 @@ interface LintContext {
21
24
  data?: Record<string, Json>;
22
25
  }) => void;
23
26
  }
27
+ interface LintScope {
28
+ set?: Map<string, any>;
29
+ upper?: LintScope;
30
+ }
24
31
  declare const plugin: {
25
32
  meta: {
26
33
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;AAikBD,QAAA,MAAM,MAAM;;;;;;;;;;;;CAUX,CAAA;AAED,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE;QACX,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,GAAG,SAAS,CAAA;KACpD,CAAA;IACD,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;AAED,UAAU,SAAS;IACjB,GAAG,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtB,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AA8zBD,QAAA,MAAM,MAAM;;;;;;;;;;;;CAUX,CAAA;AAED,eAAe,MAAM,CAAA"}
package/dist/lint.js CHANGED
@@ -26,6 +26,14 @@ var ELEMENT_PRIMITIVES = Object.freeze({
26
26
  path: "@adea-ai/ui/components/ui/label"
27
27
  }
28
28
  });
29
+ var variableOf = (node, context) => {
30
+ if (node?.type !== "Identifier" && node?.type !== "JSXIdentifier") return null;
31
+ for (let scope = context.sourceCode?.getScope?.(node); scope; scope = scope.upper) {
32
+ const variable = scope.set?.get(node.name);
33
+ if (variable) return variable;
34
+ }
35
+ return null;
36
+ };
29
37
  /** Reads a static object-property key without evaluating a computed expression. */
30
38
  var staticPropertyKey = (node, computed) => {
31
39
  if (!computed && node?.type === "Identifier") return node.name;
@@ -121,7 +129,7 @@ var noClassList = {
121
129
  var noRawInteractiveElements = {
122
130
  meta: {
123
131
  type: "problem",
124
- docs: { description: "Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup." },
132
+ docs: { description: "Interactive elements this package provides (button, input, textarea, select, option, label), including statically known Solid Dynamic tags, are composed from the package, not written as raw markup." },
125
133
  schema: [{
126
134
  type: "object",
127
135
  properties: {
@@ -138,6 +146,10 @@ var noRawInteractiveElements = {
138
146
  create(context) {
139
147
  const options = context.options?.[0] ?? {};
140
148
  const allowed = new Set(options.allow ?? []);
149
+ const dynamicImports = {
150
+ named: /* @__PURE__ */ new WeakSet(),
151
+ namespaces: /* @__PURE__ */ new WeakSet()
152
+ };
141
153
  const emit = (node, element) => {
142
154
  const target = ELEMENT_PRIMITIVES[element];
143
155
  context.report({
@@ -151,12 +163,17 @@ var noRawInteractiveElements = {
151
163
  ...options.message ? { message: options.message } : {}
152
164
  });
153
165
  };
154
- return { JSXOpeningElement(node) {
155
- if (node.name?.type !== "JSXIdentifier") return;
156
- const element = node.name.name;
157
- if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return;
158
- emit(node, element);
159
- } };
166
+ return {
167
+ Program(node) {
168
+ collectSolidDynamicImports(node, dynamicImports);
169
+ },
170
+ JSXOpeningElement(node) {
171
+ for (const element of jsxElementNames(node, dynamicImports, context)) {
172
+ if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) continue;
173
+ emit(node, element);
174
+ }
175
+ }
176
+ };
160
177
  }
161
178
  };
162
179
  var DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
@@ -172,20 +189,27 @@ var DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
172
189
  ]);
173
190
  /**
174
191
  * The interactive ARIA roles an application can dress a generic element in, and
175
- * the primitive that owns each role. `link` is deliberately absent: an `<a>` is
176
- * natively interactive and Button itself supports `as="a"` for the link-styled
177
- * case.
192
+ * the published primitive that owns each role. Native anchors are not generic
193
+ * elements, so their built-in link semantics are unaffected by the `link` entry.
178
194
  */
179
195
  var ROLE_PRIMITIVES = Object.freeze({
180
196
  button: {
181
197
  primitive: "Button",
182
198
  path: "@adea-ai/ui/components/ui/button"
183
199
  },
200
+ link: {
201
+ primitive: "TextLink",
202
+ path: "@adea-ai/ui/components/ui/text-link"
203
+ },
184
204
  checkbox: {
185
205
  primitive: "Checkbox",
186
206
  path: "@adea-ai/ui/components/ui/checkbox"
187
207
  },
188
208
  radio: {
209
+ primitive: "RadioGroupItem",
210
+ path: "@adea-ai/ui/components/ui/radio-group"
211
+ },
212
+ radiogroup: {
189
213
  primitive: "RadioGroup",
190
214
  path: "@adea-ai/ui/components/ui/radio-group"
191
215
  },
@@ -194,20 +218,60 @@ var ROLE_PRIMITIVES = Object.freeze({
194
218
  path: "@adea-ai/ui/components/ui/switch"
195
219
  },
196
220
  tab: {
197
- primitive: "Tabs",
221
+ primitive: "TabsTrigger",
222
+ path: "@adea-ai/ui/components/ui/tabs"
223
+ },
224
+ tablist: {
225
+ primitive: "TabsList",
226
+ path: "@adea-ai/ui/components/ui/tabs"
227
+ },
228
+ tabpanel: {
229
+ primitive: "TabsContent",
198
230
  path: "@adea-ai/ui/components/ui/tabs"
199
231
  },
200
232
  option: {
233
+ primitive: "SelectItem",
234
+ path: "@adea-ai/ui/components/ui/select"
235
+ },
236
+ listbox: {
201
237
  primitive: "Select",
202
238
  path: "@adea-ai/ui/components/ui/select"
203
239
  },
240
+ combobox: {
241
+ primitive: "Combobox",
242
+ path: "@adea-ai/ui/components/ui/combobox"
243
+ },
244
+ searchbox: {
245
+ primitive: "Input",
246
+ path: "@adea-ai/ui/components/ui/input"
247
+ },
248
+ menu: {
249
+ primitive: "DropdownMenuContent",
250
+ path: "@adea-ai/ui/components/ui/dropdown-menu"
251
+ },
204
252
  menuitem: {
205
- primitive: "DropdownMenu",
253
+ primitive: "DropdownMenuItem",
254
+ path: "@adea-ai/ui/components/ui/dropdown-menu"
255
+ },
256
+ menuitemcheckbox: {
257
+ primitive: "DropdownMenuCheckboxItem",
258
+ path: "@adea-ai/ui/components/ui/dropdown-menu"
259
+ },
260
+ menuitemradio: {
261
+ primitive: "DropdownMenuRadioItem",
206
262
  path: "@adea-ai/ui/components/ui/dropdown-menu"
207
263
  },
208
264
  textbox: {
209
265
  primitive: "Input",
210
266
  path: "@adea-ai/ui/components/ui/input"
267
+ },
268
+ tree: {
269
+ primitive: "Tree",
270
+ path: "@adea-ai/ui/components/composites/tree"
271
+ },
272
+ treeitem: {
273
+ primitive: "TreeRow",
274
+ path: "@adea-ai/ui/components/composites/tree"
211
275
  }
212
276
  });
213
277
  /**
@@ -239,37 +303,122 @@ var GENERIC_ELEMENTS = /* @__PURE__ */ new Set([
239
303
  "img",
240
304
  "svg"
241
305
  ]);
242
- var CLICK_HANDLERS = /* @__PURE__ */ new Set([
306
+ var CLICK_HANDLERS = new Set([
243
307
  "onClick",
308
+ "onDblClick",
244
309
  "onDoubleClick",
245
310
  "onPointerDown",
246
311
  "onPointerUp",
247
312
  "onMouseDown",
248
313
  "onMouseUp",
314
+ "onTouchStart",
315
+ "onTouchEnd",
249
316
  "on:click",
250
317
  "on:dblclick",
251
318
  "on:pointerdown",
252
319
  "on:pointerup",
253
320
  "on:mousedown",
254
- "on:mouseup"
255
- ]);
256
- /** Only statically knowable branches; unresolved roles remain a review boundary. */
257
- var staticStrings = (node) => {
321
+ "on:mouseup",
322
+ "on:touchstart",
323
+ "on:touchend"
324
+ ].flatMap((handler) => [handler, handler.toLowerCase()]));
325
+ /** Returns a const binding's immutable initializer, when its value is statically knowable. */
326
+ var constInitializer = (node, context) => {
327
+ const definition = variableOf(node, context)?.defs?.[0];
328
+ if (definition?.type !== "Variable" || definition.parent?.kind !== "const") return null;
329
+ return definition.node?.init ?? null;
330
+ };
331
+ /** Reads literal strings through static branches and local const aliases. */
332
+ var staticStrings = (node, context, seen = /* @__PURE__ */ new Set()) => {
333
+ if (!node || seen.has(node)) return [];
334
+ seen.add(node);
335
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression") return staticStrings(node.expression, context, seen);
258
336
  if (node?.type === "Literal" && typeof node.value === "string") return [node.value];
259
- if (node?.type === "JSXExpressionContainer") return staticStrings(node.expression);
260
- if (node?.type === "ConditionalExpression") return [...staticStrings(node.consequent), ...staticStrings(node.alternate)];
337
+ if (node?.type === "JSXExpressionContainer") return staticStrings(node.expression, context, seen);
338
+ if (node?.type === "ConditionalExpression") return [...staticStrings(node.consequent, context, new Set(seen)), ...staticStrings(node.alternate, context, new Set(seen))];
339
+ if (node?.type === "LogicalExpression") return [...staticStrings(node.left, context, new Set(seen)), ...staticStrings(node.right, context, new Set(seen))];
261
340
  if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw];
341
+ if (node?.type === "Identifier" && context) {
342
+ const initializer = constInitializer(node, context);
343
+ return initializer ? staticStrings(initializer, context, seen) : [];
344
+ }
262
345
  return [];
263
346
  };
264
- var roleOf = (node) => {
347
+ var collectSolidDynamicImport = (node, imports) => {
348
+ if (node.source?.value !== "solid-js/web") return;
349
+ for (const specifier of node.specifiers ?? []) {
350
+ if (specifier.type === "ImportNamespaceSpecifier" && specifier.local) imports.namespaces.add(specifier.local);
351
+ if (specifier.type !== "ImportSpecifier") continue;
352
+ if ((specifier.imported?.name ?? specifier.imported?.value) === "Dynamic" && specifier.local) imports.named.add(specifier.local);
353
+ }
354
+ };
355
+ var collectSolidDynamicImports = (program, imports) => {
356
+ for (const statement of program.body ?? []) if (statement.type === "ImportDeclaration") collectSolidDynamicImport(statement, imports);
357
+ };
358
+ var isImportedBinding = (node, bindings, context) => variableOf(node, context)?.identifiers?.some((identifier) => bindings.has(identifier)) ?? false;
359
+ var isSolidDynamic = (name, imports, context) => {
360
+ if (name?.type === "JSXIdentifier") return isImportedBinding(name, imports.named, context);
361
+ return name?.type === "JSXMemberExpression" && name.property?.name === "Dynamic" && name.object?.type === "JSXIdentifier" && isImportedBinding(name.object, imports.namespaces, context);
362
+ };
363
+ var valuesIncludeKnownIntrinsic = (values, context) => values.flatMap((value) => staticStrings(value, context)).some((element) => GENERIC_ELEMENTS.has(element) || element in ELEMENT_PRIMITIVES);
364
+ /** Applies `component` assignments made by a spread, tracking opaque overrides. */
365
+ var applyDynamicComponentSpread = (node, resolution, context) => {
366
+ while (node?.type === "TSAsExpression" || node?.type === "TSSatisfiesExpression" || node?.type === "TSNonNullExpression") node = node.expression;
367
+ if (!node || node.type !== "ObjectExpression") return {
368
+ values: [],
369
+ opaqueAfterKnownIntrinsic: resolution.opaqueAfterKnownIntrinsic || valuesIncludeKnownIntrinsic(resolution.values, context)
370
+ };
371
+ let current = resolution;
372
+ for (const property of node.properties ?? []) {
373
+ if (property.type === "SpreadElement") {
374
+ current = applyDynamicComponentSpread(property.argument, current, context);
375
+ continue;
376
+ }
377
+ if (property.type !== "Property") continue;
378
+ const key = staticPropertyKey(property.key, property.computed === true);
379
+ if (key === "component") current = {
380
+ values: [property.value],
381
+ opaqueAfterKnownIntrinsic: false
382
+ };
383
+ else if (property.computed && key === null) current = {
384
+ values: [],
385
+ opaqueAfterKnownIntrinsic: current.opaqueAfterKnownIntrinsic || valuesIncludeKnownIntrinsic(current.values, context)
386
+ };
387
+ }
388
+ return current;
389
+ };
390
+ var dynamicComponentResolution = (node, context) => {
391
+ let resolution = {
392
+ values: [],
393
+ opaqueAfterKnownIntrinsic: false
394
+ };
395
+ for (const attribute of node.attributes ?? []) {
396
+ if (attribute.type === "JSXAttribute" && attribute.name?.name === "component") {
397
+ resolution = {
398
+ values: attribute.value ? [attribute.value] : [],
399
+ opaqueAfterKnownIntrinsic: false
400
+ };
401
+ continue;
402
+ }
403
+ if (attribute.type === "JSXSpreadAttribute") resolution = applyDynamicComponentSpread(attribute.argument, resolution, context);
404
+ }
405
+ return resolution;
406
+ };
407
+ /** Resolves native tags passed to Solid Dynamic while leaving component values alone. */
408
+ var jsxElementNames = (node, imports, context) => {
409
+ if (isSolidDynamic(node.name, imports, context)) return [...new Set(dynamicComponentResolution(node, context).values.flatMap((value) => staticStrings(value, context)))];
410
+ if (node.name?.type === "JSXIdentifier") return [node.name.name];
411
+ return [];
412
+ };
413
+ var roleOf = (node, context) => {
265
414
  for (const attribute of node.attributes ?? []) {
266
415
  if (attribute.type === "JSXSpreadAttribute") {
267
- const role = staticObjectPropertyValues(attribute.argument, "role").flatMap((value) => staticStrings(value)).flatMap((value) => value.split(/\s+/)).find((value) => value in ROLE_PRIMITIVES);
416
+ const role = staticObjectPropertyValues(attribute.argument, "role").flatMap((value) => staticStrings(value, context)).flatMap((value) => value.split(/\s+/)).find((value) => value in ROLE_PRIMITIVES);
268
417
  if (role) return role;
269
418
  continue;
270
419
  }
271
420
  if (attribute.type !== "JSXAttribute" || attribute.name?.name !== "role") continue;
272
- return staticStrings(attribute.value).flatMap((role) => role.split(/\s+/)).find((role) => role in ROLE_PRIMITIVES) ?? null;
421
+ return staticStrings(attribute.value, context).flatMap((role) => role.split(/\s+/)).find((role) => role in ROLE_PRIMITIVES) ?? null;
273
422
  }
274
423
  return null;
275
424
  };
@@ -286,6 +435,18 @@ var hasTabStop = (node) => (node.attributes ?? []).some((attribute) => {
286
435
  if (attribute.type === "JSXSpreadAttribute") return ["tabIndex", "tabindex"].some((name) => staticObjectPropertyValues(attribute.argument, name).some(tabStopValue));
287
436
  return false;
288
437
  });
438
+ /** Unknown/computed props make a JSX object spread opaque to interaction checks. */
439
+ var opaqueSpread = (node, seen = /* @__PURE__ */ new Set()) => {
440
+ while (node?.type === "TSAsExpression" || node?.type === "TSSatisfiesExpression" || node?.type === "TSNonNullExpression") node = node.expression;
441
+ if (!node || seen.has(node) || node.type !== "ObjectExpression") return true;
442
+ seen.add(node);
443
+ return node.properties.some((property) => {
444
+ if (property.type === "SpreadElement") return opaqueSpread(property.argument, seen);
445
+ if (property.type !== "Property") return true;
446
+ return staticPropertyKey(property.key, property.computed === true) === null;
447
+ });
448
+ };
449
+ var hasOpaqueSpread = (node) => (node.attributes ?? []).some((attribute) => attribute.type === "JSXSpreadAttribute" && opaqueSpread(attribute.argument));
289
450
  var noInteractiveWrappers = {
290
451
  meta: {
291
452
  type: "problem",
@@ -297,6 +458,7 @@ var noInteractiveWrappers = {
297
458
  type: "array",
298
459
  items: { type: "string" }
299
460
  },
461
+ rejectUnknownSpreads: { type: "boolean" },
300
462
  message: { type: "string" }
301
463
  },
302
464
  additionalProperties: false
@@ -304,56 +466,85 @@ var noInteractiveWrappers = {
304
466
  messages: {
305
467
  roleWrapper: "A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.",
306
468
  clickWrapper: "A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.",
307
- tabindexWrapper: "A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead."
469
+ tabindexWrapper: "A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.",
470
+ opaqueSpreadWrapper: "A spread on {{element}} may hide a role, activation handler, or tab stop. Pass statically named props or compose the shared component instead.",
471
+ opaqueDynamicComponentWrapper: "An opaque spread follows a known raw design-system or generic tag on Solid Dynamic and may replace the tag or hide interactive props. Pass statically named props or a shared component so the interaction rule can inspect the final component."
308
472
  }
309
473
  },
310
474
  create(context) {
311
475
  const options = context.options?.[0] ?? {};
312
476
  const allowed = new Set(options.allow ?? []);
313
- return { JSXOpeningElement(node) {
314
- if (node.name?.type !== "JSXIdentifier") return;
315
- const element = node.name.name;
316
- if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return;
317
- if (element[0] === element[0]?.toUpperCase()) return;
318
- const role = roleOf(node);
319
- if (role && role in ROLE_PRIMITIVES) {
320
- const target = ROLE_PRIMITIVES[role];
321
- context.report({
322
- node,
323
- messageId: "roleWrapper",
324
- data: {
325
- element,
326
- role,
327
- primitive: target.primitive,
328
- path: target.path
329
- },
330
- ...options.message ? { message: options.message } : {}
331
- });
332
- return;
333
- }
334
- if (GENERIC_ELEMENTS.has(element)) {
335
- if ([...CLICK_HANDLERS].find((handler) => has(node, handler))) {
336
- const target = ROLE_PRIMITIVES.button;
337
- context.report({
477
+ const dynamicImports = {
478
+ named: /* @__PURE__ */ new WeakSet(),
479
+ namespaces: /* @__PURE__ */ new WeakSet()
480
+ };
481
+ return {
482
+ Program(node) {
483
+ collectSolidDynamicImports(node, dynamicImports);
484
+ },
485
+ JSXOpeningElement(node) {
486
+ if (options.rejectUnknownSpreads && isSolidDynamic(node.name, dynamicImports, context)) {
487
+ if (dynamicComponentResolution(node, context).opaqueAfterKnownIntrinsic) {
488
+ context.report({
489
+ node,
490
+ messageId: "opaqueDynamicComponentWrapper",
491
+ ...options.message ? { message: options.message } : {}
492
+ });
493
+ return;
494
+ }
495
+ }
496
+ for (const element of jsxElementNames(node, dynamicImports, context)) {
497
+ if (allowed.has(element) || element in ELEMENT_PRIMITIVES) continue;
498
+ if (element[0] === element[0]?.toUpperCase()) continue;
499
+ if (!GENERIC_ELEMENTS.has(element)) continue;
500
+ if (options.rejectUnknownSpreads && hasOpaqueSpread(node)) {
501
+ context.report({
502
+ node,
503
+ messageId: "opaqueSpreadWrapper",
504
+ data: { element },
505
+ ...options.message ? { message: options.message } : {}
506
+ });
507
+ break;
508
+ }
509
+ const role = roleOf(node, context);
510
+ if (role && role in ROLE_PRIMITIVES) {
511
+ const target = ROLE_PRIMITIVES[role];
512
+ context.report({
513
+ node,
514
+ messageId: "roleWrapper",
515
+ data: {
516
+ element,
517
+ role,
518
+ primitive: target.primitive,
519
+ path: target.path
520
+ },
521
+ ...options.message ? { message: options.message } : {}
522
+ });
523
+ continue;
524
+ }
525
+ if ([...CLICK_HANDLERS].find((handler) => has(node, handler))) {
526
+ const target = ROLE_PRIMITIVES.button;
527
+ context.report({
528
+ node,
529
+ messageId: "clickWrapper",
530
+ data: {
531
+ element,
532
+ primitive: target.primitive,
533
+ path: target.path
534
+ },
535
+ ...options.message ? { message: options.message } : {}
536
+ });
537
+ continue;
538
+ }
539
+ if (hasTabStop(node)) context.report({
338
540
  node,
339
- messageId: "clickWrapper",
340
- data: {
341
- element,
342
- primitive: target.primitive,
343
- path: target.path
344
- },
541
+ messageId: "tabindexWrapper",
542
+ data: { element },
345
543
  ...options.message ? { message: options.message } : {}
346
544
  });
347
- return;
348
545
  }
349
- if (hasTabStop(node)) context.report({
350
- node,
351
- messageId: "tabindexWrapper",
352
- data: { element },
353
- ...options.message ? { message: options.message } : {}
354
- });
355
546
  }
356
- } };
547
+ };
357
548
  }
358
549
  };
359
550
  var BUTTON_MODULE = "@adea-ai/ui/components/ui/button";
package/dist/lint.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\n/** Reads a static object-property key without evaluating a computed expression. */\nconst staticPropertyKey = (node: any, computed: boolean): string | null => {\n if (!computed && node?.type === 'Identifier') return node.name\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\n/** Detects named props in a literal JSX spread, including nested literal spreads. */\nconst objectHasStaticProperty = (node: any, name: string, seen = new Set<unknown>()): boolean => {\n if (!node || seen.has(node)) return false\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return objectHasStaticProperty(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return false\n return node.properties.some((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n if (property.type === 'SpreadElement')\n return objectHasStaticProperty(property.argument, name, seen)\n return false\n })\n}\n\n/** Returns literal values for a statically named property in an object spread. */\nconst staticObjectPropertyValues = (node: any, name: string, seen = new Set<unknown>()): any[] => {\n if (!node || seen.has(node)) return []\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return staticObjectPropertyValues(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return []\n return node.properties.flatMap((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n ? [property.value]\n : []\n if (property.type === 'SpreadElement')\n return staticObjectPropertyValues(property.argument, name, seen)\n return []\n })\n}\n\nconst noInlineStyles: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer components cannot set appearance or layout with JSX style props; use shared variants and token-backed classes.',\n },\n schema: [],\n messages: {\n inlineStyle:\n 'JSX style attributes are not permitted, including layout values and CSS custom properties. Use shared variants and token-backed classes.',\n inlineStyleElement:\n 'A JSX style element bypasses the shared design system. Use shared variants and token-backed classes.',\n inlineStyleSpread:\n 'A JSX spread contains a style prop. Pass named shared component props instead of hiding inline styles in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyle' })\n },\n JSXOpeningElement(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyleElement' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'style'))\n context.report({ node, messageId: 'inlineStyleSpread' })\n },\n }\n },\n}\n\nconst noClassList: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Solid classList props hide conditional classes from static design-system lint; use cn() with its object-key form.',\n },\n schema: [],\n messages: {\n classList:\n 'classList hides classes from design-system lint. Use cn() with its object-key form and statically readable class names.',\n classListSpread:\n 'A JSX spread contains classList. Use cn() with its object-key form instead of hiding classes in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'classList')\n context.report({ node, messageId: 'classList' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'classList'))\n context.report({ node, messageId: 'classListSpread' })\n },\n }\n },\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set([\n 'onClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Only statically knowable branches; unresolved roles remain a review boundary. */\nconst staticStrings = (node: any): string[] => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)\n if (node?.type === 'ConditionalExpression')\n return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n return []\n}\n\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXSpreadAttribute') {\n const role = staticObjectPropertyValues(attribute.argument, 'role')\n .flatMap((value) => staticStrings(value))\n .flatMap((value) => value.split(/\\s+/))\n .find((value) => value in ROLE_PRIMITIVES)\n if (role) return role\n continue\n }\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n (attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name) ||\n (attribute.type === 'JSXSpreadAttribute' && objectHasStaticProperty(attribute.argument, name))\n )\n\nconst tabStopValue = (value: any): boolean => {\n const expression = value?.type === 'JSXExpressionContainer' ? value.expression : value\n if (expression?.type === 'Literal') return !(Number(expression.value) < 0)\n if (\n expression?.type === 'UnaryExpression' &&\n expression.operator === '-' &&\n expression.argument?.type === 'Literal'\n )\n return !(Number(expression.argument.value) > 0)\n return true\n}\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type === 'JSXAttribute' &&\n ['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return tabStopValue(attribute.value)\n if (attribute.type === 'JSXSpreadAttribute')\n return ['tabIndex', 'tabindex'].some((name) =>\n staticObjectPropertyValues(attribute.argument, name).some(tabStopValue)\n )\n return false\n })\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n // A capitalized element is a component — often a design-system primitive\n // composing a role deliberately (a Button carrying role=\"option\" inside a\n // custom listbox, for example). That is composition, not impersonation.\n if (element[0] === element[0]?.toUpperCase()) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (hasTabStop(node)) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst BUTTON_MODULE = '@adea-ai/ui/components/ui/button'\nconst ACTION_BUTTON_MODULE = '@adea-ai/ui/components/composites/action-button'\nconst ICON_BUTTON_SIZES: ReadonlySet<string> = new Set([\n 'icon-2xs',\n 'icon-xs',\n 'icon-sm',\n 'icon-md',\n 'icon-lg',\n 'icon-xl',\n 'icon-2xl',\n])\n\nconst staticJsxValues = (value: any): any[] =>\n value?.type === 'JSXExpressionContainer' ? [value.expression] : value ? [value] : []\n\nconst attributeValues = (node: any, name: string): any[] => {\n const values: any[] = []\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXAttribute' && attribute.name?.name === name)\n values.splice(0, values.length, ...staticJsxValues(attribute.value))\n if (attribute.type === 'JSXSpreadAttribute') {\n const spreadValues = staticObjectPropertyValues(attribute.argument, name)\n if (spreadValues.length > 0) values.splice(0, values.length, spreadValues.at(-1))\n }\n }\n return values\n}\n\nconst staticStringValue = (node: any): string | null => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\nconst isIconButtonSize = (node: any): boolean =>\n attributeValues(node, 'size').some((value) => {\n const size = staticStringValue(value)\n return size !== null && ICON_BUTTON_SIZES.has(size)\n })\n\nconst actionTooltipStatus = (node: any): 'valid' | 'blank' | 'unknown' | 'missing' => {\n const values = attributeValues(node, 'tooltip')\n if (values.length === 0) return 'missing'\n const strings = values.map(staticStringValue)\n if (strings.some((value) => value !== null && value.trim().length > 0)) return 'valid'\n if (strings.every((value) => value !== null)) return 'blank'\n return 'unknown'\n}\n\nconst isImportedComponent = (identifier: any, imports: Set<string>): boolean =>\n identifier?.type === 'Identifier' && imports.has(identifier.name)\n\n/** Requires shared icon actions to carry the shared explanatory-tooltip contract. */\nconst requireActionButtonTooltip: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer icon-size shared Button actions use ActionButton with a nonblank tooltip; dynamic values remain a review boundary.',\n },\n schema: [],\n messages: {\n useActionButton:\n 'Icon-size Button actions use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.',\n missingTooltip:\n 'An icon-size ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well.',\n },\n },\n create(context) {\n const buttons = new Set<string>()\n const actionButtons = new Set<string>()\n const recordImports = (node: any) => {\n const source = node.source?.value\n const target =\n source === BUTTON_MODULE ? buttons : source === ACTION_BUTTON_MODULE ? actionButtons : null\n if (!target) return\n for (const specifier of node.specifiers ?? []) {\n if (specifier.type !== 'ImportSpecifier') continue\n const imported = specifier.imported?.name ?? specifier.imported?.value\n if (\n (source === BUTTON_MODULE && imported === 'Button') ||\n (source === ACTION_BUTTON_MODULE && imported === 'ActionButton')\n )\n target.add(specifier.local?.name)\n }\n }\n\n return {\n ImportDeclaration: recordImports,\n JSXOpeningElement(node: any) {\n if (!isIconButtonSize(node)) return\n const name = node.name?.type === 'JSXIdentifier' ? node.name.name : null\n const isButton = name !== null && buttons.has(name)\n const isActionButton = name !== null && actionButtons.has(name)\n const asValues = attributeValues(node, 'as')\n const targetsButton = asValues.some((value) => isImportedComponent(value, buttons))\n const targetsActionButton = asValues.some((value) =>\n isImportedComponent(value, actionButtons)\n )\n if (isButton || targetsButton) {\n context.report({ node, messageId: 'useActionButton' })\n return\n }\n if (!isActionButton && !targetsActionButton) return\n const status = actionTooltipStatus(node)\n if (status === 'missing' || status === 'blank')\n context.report({ node, messageId: 'missingTooltip' })\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-inline-styles': noInlineStyles,\n 'no-class-list': noClassList,\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n 'require-action-button-tooltip': requireActionButtonTooltip,\n },\n}\n\nexport default plugin\n"],"mappings":";;AAsBA,IAAM,qBACJ,OAAO,OAAO;CACZ,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,OAAO;EAAE,WAAW;EAAS,MAAM;CAAkC;CACrE,UAAU;EAAE,WAAW;EAAY,MAAM;CAAqC;CAC9E,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,OAAO;EAAE,WAAW;EAAS,MAAM;CAAkC;AACvE,CAAC;;AAuBH,IAAM,qBAAqB,MAAW,aAAqC;CACzE,IAAI,CAAC,YAAY,MAAM,SAAS,cAAc,OAAO,KAAK;CAC1D,IAAI,MAAM,SAAS,aAAa,OAAO,KAAK,UAAU,UAAU,OAAO,KAAK;CAC5E,IAAI,MAAM,SAAS,qBAAqB,KAAK,YAAY,WAAW,GAClE,OAAO,KAAK,OAAO,EAAE,CAAC,MAAM,UAAU,KAAK,OAAO,EAAE,CAAC,MAAM;CAC7D,OAAO;AACT;;AAGA,IAAM,2BAA2B,MAAW,MAAc,uBAAO,IAAI,IAAa,MAAe;CAC/F,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,GAAG,OAAO;CACpC,KAAK,IAAI,IAAI;CACb,IACE,KAAK,SAAS,oBACd,KAAK,SAAS,2BACd,KAAK,SAAS,uBAEd,OAAO,wBAAwB,KAAK,YAAY,MAAM,IAAI;CAC5D,IAAI,KAAK,SAAS,oBAAoB,OAAO;CAC7C,OAAO,KAAK,WAAW,MAAM,aAAkB;EAC7C,IAAI,SAAS,SAAS,YACpB,OAAO,kBAAkB,SAAS,KAAK,SAAS,aAAa,IAAI,MAAM;EACzE,IAAI,SAAS,SAAS,iBACpB,OAAO,wBAAwB,SAAS,UAAU,MAAM,IAAI;EAC9D,OAAO;CACT,CAAC;AACH;;AAGA,IAAM,8BAA8B,MAAW,MAAc,uBAAO,IAAI,IAAa,MAAa;CAChG,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC;CACrC,KAAK,IAAI,IAAI;CACb,IACE,KAAK,SAAS,oBACd,KAAK,SAAS,2BACd,KAAK,SAAS,uBAEd,OAAO,2BAA2B,KAAK,YAAY,MAAM,IAAI;CAC/D,IAAI,KAAK,SAAS,oBAAoB,OAAO,CAAC;CAC9C,OAAO,KAAK,WAAW,SAAS,aAAkB;EAChD,IAAI,SAAS,SAAS,YACpB,OAAO,kBAAkB,SAAS,KAAK,SAAS,aAAa,IAAI,MAAM,OACnE,CAAC,SAAS,KAAK,IACf,CAAC;EACP,IAAI,SAAS,SAAS,iBACpB,OAAO,2BAA2B,SAAS,UAAU,MAAM,IAAI;EACjE,OAAO,CAAC;CACV,CAAC;AACH;AAEA,IAAM,iBAA6B;CACjC,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,0HACJ;EACA,QAAQ,CAAC;EACT,UAAU;GACR,aACE;GACF,oBACE;GACF,mBACE;EACJ;CACF;CACA,OAAO,SAAS;EACd,OAAO;GACL,aAAa,MAAW;IACtB,IAAI,KAAK,MAAM,SAAS,mBAAmB,KAAK,KAAK,SAAS,SAC5D,QAAQ,OAAO;KAAE;KAAM,WAAW;IAAc,CAAC;GACrD;GACA,kBAAkB,MAAW;IAC3B,IAAI,KAAK,MAAM,SAAS,mBAAmB,KAAK,KAAK,SAAS,SAC5D,QAAQ,OAAO;KAAE;KAAM,WAAW;IAAqB,CAAC;GAC5D;GACA,mBAAmB,MAAW;IAC5B,IAAI,wBAAwB,KAAK,UAAU,OAAO,GAChD,QAAQ,OAAO;KAAE;KAAM,WAAW;IAAoB,CAAC;GAC3D;EACF;CACF;AACF;AAEA,IAAM,cAA0B;CAC9B,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oHACJ;EACA,QAAQ,CAAC;EACT,UAAU;GACR,WACE;GACF,iBACE;EACJ;CACF;CACA,OAAO,SAAS;EACd,OAAO;GACL,aAAa,MAAW;IACtB,IAAI,KAAK,MAAM,SAAS,mBAAmB,KAAK,KAAK,SAAS,aAC5D,QAAQ,OAAO;KAAE;KAAM,WAAW;IAAY,CAAC;GACnD;GACA,mBAAmB,MAAW;IAC5B,IAAI,wBAAwB,KAAK,UAAU,WAAW,GACpD,QAAQ,OAAO;KAAE;KAAM,WAAW;IAAkB,CAAC;GACzD;EACF;CACF;AACF;AASA,IAAM,2BAAuC;CAC3C,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,wJACJ;EACA,QAAQ,CACN;GACE,MAAM;GACN,YAAY;IACV,OAAO;KAAE,MAAM;KAAS,OAAO,EAAE,MAAM,SAAS;IAAE;IAClD,SAAS,EAAE,MAAM,SAAS;GAC5B;GACA,sBAAsB;EACxB,CACF;EACA,UAAU,EACR,YACE,4MACJ;CACF;CACA,OAAO,SAAS;EACd,MAAM,UAAW,QAAQ,UAAU,MAAM,CAAC;EAC1C,MAAM,UAAU,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC;EAC3C,MAAM,QAAQ,MAAe,YAAoB;GAC/C,MAAM,SAAS,mBAAmB;GAClC,QAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KAAE;KAAS,WAAW,OAAO;KAAW,MAAM,OAAO;IAAK;IAChE,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;GACxD,CAAC;EACH;EACA,OAAO,EACL,kBAAkB,MAAW;GAC3B,IAAI,KAAK,MAAM,SAAS,iBAAiB;GACzC,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,EAAE,WAAW,uBAAuB,QAAQ,IAAI,OAAO,GAAG;GAC9D,KAAK,MAAM,OAAO;EACpB,EACF;CACF;AACF;AASA,IAAM,6BAA6B,OAAO,OAAO;CAC/C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;AAQD,IAAM,kBACJ,OAAO,OAAO;CACZ,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,UAAU;EAAE,WAAW;EAAY,MAAM;CAAqC;CAC9E,OAAO;EAAE,WAAW;EAAc,MAAM;CAAwC;CAChF,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,KAAK;EAAE,WAAW;EAAQ,MAAM;CAAiC;CACjE,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,UAAU;EAAE,WAAW;EAAgB,MAAM;CAA0C;CACvF,SAAS;EAAE,WAAW;EAAS,MAAM;CAAkC;AACzE,CAAC;;;;;AAMH,IAAM,mCAAwC,IAAI,IAAI;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,iCAAsC,IAAI,IAAI;CAClD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAUD,IAAM,iBAAiB,SAAwB;CAC7C,IAAI,MAAM,SAAS,aAAa,OAAO,KAAK,UAAU,UAAU,OAAO,CAAC,KAAK,KAAK;CAClF,IAAI,MAAM,SAAS,0BAA0B,OAAO,cAAc,KAAK,UAAU;CACjF,IAAI,MAAM,SAAS,yBACjB,OAAO,CAAC,GAAG,cAAc,KAAK,UAAU,GAAG,GAAG,cAAc,KAAK,SAAS,CAAC;CAC7E,IAAI,MAAM,SAAS,qBAAqB,KAAK,YAAY,WAAW,GAClE,OAAO,CAAC,KAAK,OAAO,EAAE,CAAC,MAAM,UAAU,KAAK,OAAO,EAAE,CAAC,MAAM,GAAG;CACjE,OAAO,CAAC;AACV;AAEA,IAAM,UAAU,SAA6B;CAC3C,KAAK,MAAM,aAAa,KAAK,cAAc,CAAC,GAAG;EAC7C,IAAI,UAAU,SAAS,sBAAsB;GAC3C,MAAM,OAAO,2BAA2B,UAAU,UAAU,MAAM,CAAC,CAChE,SAAS,UAAU,cAAc,KAAK,CAAC,CAAC,CACxC,SAAS,UAAU,MAAM,MAAM,KAAK,CAAC,CAAC,CACtC,MAAM,UAAU,SAAS,eAAe;GAC3C,IAAI,MAAM,OAAO;GACjB;EACF;EACA,IAAI,UAAU,SAAS,kBAAkB,UAAU,MAAM,SAAS,QAAQ;EAC1E,OACE,cAAc,UAAU,KAAK,CAAC,CAC3B,SAAS,SAAS,KAAK,MAAM,KAAK,CAAC,CAAC,CACpC,MAAM,SAAS,QAAQ,eAAe,KAAK;CAElD;CACA,OAAO;AACT;;AAGA,IAAM,OAAO,MAAW,UACrB,KAAK,cAAc,CAAC,EAAA,CAAG,MACrB,cACE,UAAU,SAAS,mBACjB,UAAU,MAAM,SAAS,sBACtB,GAAG,UAAU,KAAK,UAAU,KAAK,GAAG,UAAU,KAAK,KAAK,SACxD,UAAU,MAAM,UAAU,QAC/B,UAAU,SAAS,wBAAwB,wBAAwB,UAAU,UAAU,IAAI,CAChG;AAEF,IAAM,gBAAgB,UAAwB;CAC5C,MAAM,aAAa,OAAO,SAAS,2BAA2B,MAAM,aAAa;CACjF,IAAI,YAAY,SAAS,WAAW,OAAO,EAAE,OAAO,WAAW,KAAK,IAAI;CACxE,IACE,YAAY,SAAS,qBACrB,WAAW,aAAa,OACxB,WAAW,UAAU,SAAS,WAE9B,OAAO,EAAE,OAAO,WAAW,SAAS,KAAK,IAAI;CAC/C,OAAO;AACT;AAEA,IAAM,cAAc,UACjB,KAAK,cAAc,CAAC,EAAA,CAAG,MAAM,cAAmB;CAC/C,IACE,UAAU,SAAS,kBACnB,CAAC,YAAY,UAAU,CAAC,CAAC,SAAS,UAAU,MAAM,IAAI,GAEtD,OAAO,aAAa,UAAU,KAAK;CACrC,IAAI,UAAU,SAAS,sBACrB,OAAO,CAAC,YAAY,UAAU,CAAC,CAAC,MAAM,SACpC,2BAA2B,UAAU,UAAU,IAAI,CAAC,CAAC,KAAK,YAAY,CACxE;CACF,OAAO;AACT,CAAC;AAEH,IAAM,wBAAoC;CACxC,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,4LACJ;EACA,QAAQ,CACN;GACE,MAAM;GACN,YAAY;IACV,OAAO;KAAE,MAAM;KAAS,OAAO,EAAE,MAAM,SAAS;IAAE;IAClD,SAAS,EAAE,MAAM,SAAS;GAC5B;GACA,sBAAsB;EACxB,CACF;EACA,UAAU;GACR,aACE;GACF,cACE;GACF,iBACE;EACJ;CACF;CACA,OAAO,SAAS;EACd,MAAM,UAAW,QAAQ,UAAU,MAAM,CAAC;EAC1C,MAAM,UAAU,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC;EAC3C,OAAO,EACL,kBAAkB,MAAW;GAC3B,IAAI,KAAK,MAAM,SAAS,iBAAiB;GACzC,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,QAAQ,IAAI,OAAO,KAAK,WAAW,oBAAoB;GAI3D,IAAI,QAAQ,OAAO,QAAQ,EAAE,EAAE,YAAY,GAAG;GAE9C,MAAM,OAAO,OAAO,IAAI;GACxB,IAAI,QAAQ,QAAQ,iBAAiB;IACnC,MAAM,SAAS,gBAAgB;IAC/B,QAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAS;MAAM,WAAW,OAAO;MAAW,MAAM,OAAO;KAAK;KACtE,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;IACxD,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,IAAI,OAAO,GAAG;IAEjC,IADc,CAAC,GAAG,cAAc,CAAC,CAAC,MAAM,YAAY,IAAI,MAAM,OAAO,CACjE,GAAO;KACT,MAAM,SAAS,gBAAgB;KAC/B,QAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE;OAAS,WAAW,OAAO;OAAW,MAAM,OAAO;MAAK;MAChE,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;KACxD,CAAC;KACD;IACF;IACA,IAAI,WAAW,IAAI,GACjB,QAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,QAAQ;KAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;IACxD,CAAC;GAEL;EACF,EACF;CACF;AACF;AAEA,IAAM,gBAAgB;AACtB,IAAM,uBAAuB;AAC7B,IAAM,oCAAyC,IAAI,IAAI;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,mBAAmB,UACvB,OAAO,SAAS,2BAA2B,CAAC,MAAM,UAAU,IAAI,QAAQ,CAAC,KAAK,IAAI,CAAC;AAErF,IAAM,mBAAmB,MAAW,SAAwB;CAC1D,MAAM,SAAgB,CAAC;CACvB,KAAK,MAAM,aAAa,KAAK,cAAc,CAAC,GAAG;EAC7C,IAAI,UAAU,SAAS,kBAAkB,UAAU,MAAM,SAAS,MAChE,OAAO,OAAO,GAAG,OAAO,QAAQ,GAAG,gBAAgB,UAAU,KAAK,CAAC;EACrE,IAAI,UAAU,SAAS,sBAAsB;GAC3C,MAAM,eAAe,2BAA2B,UAAU,UAAU,IAAI;GACxE,IAAI,aAAa,SAAS,GAAG,OAAO,OAAO,GAAG,OAAO,QAAQ,aAAa,GAAG,EAAE,CAAC;EAClF;CACF;CACA,OAAO;AACT;AAEA,IAAM,qBAAqB,SAA6B;CACtD,IAAI,MAAM,SAAS,aAAa,OAAO,KAAK,UAAU,UAAU,OAAO,KAAK;CAC5E,IAAI,MAAM,SAAS,qBAAqB,KAAK,YAAY,WAAW,GAClE,OAAO,KAAK,OAAO,EAAE,CAAC,MAAM,UAAU,KAAK,OAAO,EAAE,CAAC,MAAM;CAC7D,OAAO;AACT;AAEA,IAAM,oBAAoB,SACxB,gBAAgB,MAAM,MAAM,CAAC,CAAC,MAAM,UAAU;CAC5C,MAAM,OAAO,kBAAkB,KAAK;CACpC,OAAO,SAAS,QAAQ,kBAAkB,IAAI,IAAI;AACpD,CAAC;AAEH,IAAM,uBAAuB,SAAyD;CACpF,MAAM,SAAS,gBAAgB,MAAM,SAAS;CAC9C,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,MAAM,UAAU,OAAO,IAAI,iBAAiB;CAC5C,IAAI,QAAQ,MAAM,UAAU,UAAU,QAAQ,MAAM,KAAK,CAAC,CAAC,SAAS,CAAC,GAAG,OAAO;CAC/E,IAAI,QAAQ,OAAO,UAAU,UAAU,IAAI,GAAG,OAAO;CACrD,OAAO;AACT;AAEA,IAAM,uBAAuB,YAAiB,YAC5C,YAAY,SAAS,gBAAgB,QAAQ,IAAI,WAAW,IAAI;AA2HlE,IAAM,SAAS;CACb,MAAM,EAAE,MAAM,OAAO;CACrB,OAAO;EACL,oBAAoB;EACpB,iBAAiB;EACjB,+BAA+B;EAC/B,gCAAgC;GAlElC,MAAM;IACJ,MAAM;IACN,MAAM,EACJ,aACE,6KACJ;IACA,QAAQ,CACN;KACE,MAAM;KACN,YAAY;MACV,UAAU;OAAE,MAAM;OAAS,OAAO,EAAE,MAAM,SAAS;MAAE;MACrD,SAAS,EAAE,MAAM,SAAS;KAC5B;KACA,sBAAsB;IACxB,CACF;IACA,UAAU,EACR,iBACE,6JACJ;GACF;GACA,OAAO,SAAS;IACd,MAAM,UAAW,QAAQ,UAAU,MAAM,CAAC;IAC1C,MAAM,WAAW,QAAQ,YAAY;IACrC,MAAM,YAAY,WAAmC;KACnD,MAAM,OAAO,OAAO,UAAU,EAAE;KAChC,OAAO,SAAS,MAAM,WAAW,SAAS,UAAU,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC,KAAK;IACxF;IACA,MAAM,UAAU,MAAe,WAAoB;KACjD,MAAM,MAAM,SAAS,MAAM;KAC3B,QAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,SAAS,IAAI;MACrB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;KACxD,CAAC;IACH;IACA,OAAO;KACL,kBAAkB,MAAW;MAC3B,IAAI,SAAS,KAAK,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,OAAO,KAAK;KAClE;KACA,iBAAiB,MAAW;MAC1B,IAAI,OAAO,KAAK,QAAQ,UAAU,YAAY,SAAS,KAAK,OAAO,KAAK,GACtE,OAAO,MAAM,KAAK,OAAO,KAAK;KAClC;KACA,uBAAuB,MAAW;MAChC,IAAI,SAAS,KAAK,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,OAAO,KAAK;KAClE;KACA,qBAAqB,MAAW;MAC9B,IAAI,SAAS,KAAK,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,OAAO,KAAK;KAClE;KACA,eAAe,MAAW;MACxB,IAAI,KAAK,QAAQ,SAAS,gBAAgB,KAAK,OAAO,SAAS,WAAW;MAC1E,MAAM,SAAS,KAAK,YAAY,EAAE,EAAE;MACpC,IAAI,OAAO,WAAW,YAAY,SAAS,MAAM,GAAG,OAAO,MAAM,MAAM;KACzE;IACF;GACF;EASkC;EAChC,2BAA2B;EAC3B,iCAAiC;GA/HnC,MAAM;IACJ,MAAM;IACN,MAAM,EACJ,aACE,8HACJ;IACA,QAAQ,CAAC;IACT,UAAU;KACR,iBACE;KACF,gBACE;IACJ;GACF;GACA,OAAO,SAAS;IACd,MAAM,0BAAU,IAAI,IAAY;IAChC,MAAM,gCAAgB,IAAI,IAAY;IACtC,MAAM,iBAAiB,SAAc;KACnC,MAAM,SAAS,KAAK,QAAQ;KAC5B,MAAM,SACJ,WAAW,gBAAgB,UAAU,WAAW,uBAAuB,gBAAgB;KACzF,IAAI,CAAC,QAAQ;KACb,KAAK,MAAM,aAAa,KAAK,cAAc,CAAC,GAAG;MAC7C,IAAI,UAAU,SAAS,mBAAmB;MAC1C,MAAM,WAAW,UAAU,UAAU,QAAQ,UAAU,UAAU;MACjE,IACG,WAAW,iBAAiB,aAAa,YACzC,WAAW,wBAAwB,aAAa,gBAEjD,OAAO,IAAI,UAAU,OAAO,IAAI;KACpC;IACF;IAEA,OAAO;KACL,mBAAmB;KACnB,kBAAkB,MAAW;MAC3B,IAAI,CAAC,iBAAiB,IAAI,GAAG;MAC7B,MAAM,OAAO,KAAK,MAAM,SAAS,kBAAkB,KAAK,KAAK,OAAO;MACpE,MAAM,WAAW,SAAS,QAAQ,QAAQ,IAAI,IAAI;MAClD,MAAM,iBAAiB,SAAS,QAAQ,cAAc,IAAI,IAAI;MAC9D,MAAM,WAAW,gBAAgB,MAAM,IAAI;MAC3C,MAAM,gBAAgB,SAAS,MAAM,UAAU,oBAAoB,OAAO,OAAO,CAAC;MAClF,MAAM,sBAAsB,SAAS,MAAM,UACzC,oBAAoB,OAAO,aAAa,CAC1C;MACA,IAAI,YAAY,eAAe;OAC7B,QAAQ,OAAO;QAAE;QAAM,WAAW;OAAkB,CAAC;OACrD;MACF;MACA,IAAI,CAAC,kBAAkB,CAAC,qBAAqB;MAC7C,MAAM,SAAS,oBAAoB,IAAI;MACvC,IAAI,WAAW,aAAa,WAAW,SACrC,QAAQ,OAAO;OAAE;OAAM,WAAW;MAAiB,CAAC;KACxD;IACF;GACF;EAwEmC;CACnC;AACF"}
1
+ {"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n sourceCode?: {\n getScope?: (node: unknown) => LintScope | undefined\n }\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\ninterface LintScope {\n set?: Map<string, any>\n upper?: LintScope\n}\n\ninterface LintVariable {\n defs?: { type?: string; parent?: { kind?: string }; node?: { init?: unknown } }[]\n identifiers?: object[]\n}\n\nconst variableOf = (node: any, context: LintContext): LintVariable | null => {\n if (node?.type !== 'Identifier' && node?.type !== 'JSXIdentifier') return null\n for (let scope = context.sourceCode?.getScope?.(node); scope; scope = scope.upper) {\n const variable = scope.set?.get(node.name)\n if (variable) return variable\n }\n return null\n}\n\n/** Reads a static object-property key without evaluating a computed expression. */\nconst staticPropertyKey = (node: any, computed: boolean): string | null => {\n if (!computed && node?.type === 'Identifier') return node.name\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\n/** Detects named props in a literal JSX spread, including nested literal spreads. */\nconst objectHasStaticProperty = (node: any, name: string, seen = new Set<unknown>()): boolean => {\n if (!node || seen.has(node)) return false\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return objectHasStaticProperty(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return false\n return node.properties.some((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n if (property.type === 'SpreadElement')\n return objectHasStaticProperty(property.argument, name, seen)\n return false\n })\n}\n\n/** Returns literal values for a statically named property in an object spread. */\nconst staticObjectPropertyValues = (node: any, name: string, seen = new Set<unknown>()): any[] => {\n if (!node || seen.has(node)) return []\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return staticObjectPropertyValues(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return []\n return node.properties.flatMap((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n ? [property.value]\n : []\n if (property.type === 'SpreadElement')\n return staticObjectPropertyValues(property.argument, name, seen)\n return []\n })\n}\n\nconst noInlineStyles: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer components cannot set appearance or layout with JSX style props; use shared variants and token-backed classes.',\n },\n schema: [],\n messages: {\n inlineStyle:\n 'JSX style attributes are not permitted, including layout values and CSS custom properties. Use shared variants and token-backed classes.',\n inlineStyleElement:\n 'A JSX style element bypasses the shared design system. Use shared variants and token-backed classes.',\n inlineStyleSpread:\n 'A JSX spread contains a style prop. Pass named shared component props instead of hiding inline styles in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyle' })\n },\n JSXOpeningElement(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyleElement' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'style'))\n context.report({ node, messageId: 'inlineStyleSpread' })\n },\n }\n },\n}\n\nconst noClassList: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Solid classList props hide conditional classes from static design-system lint; use cn() with its object-key form.',\n },\n schema: [],\n messages: {\n classList:\n 'classList hides classes from design-system lint. Use cn() with its object-key form and statically readable class names.',\n classListSpread:\n 'A JSX spread contains classList. Use cn() with its object-key form instead of hiding classes in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'classList')\n context.report({ node, messageId: 'classList' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'classList'))\n context.report({ node, messageId: 'classListSpread' })\n },\n }\n },\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label), including statically known Solid Dynamic tags, are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const dynamicImports: SolidDynamicImports = {\n named: new WeakSet(),\n namespaces: new WeakSet(),\n }\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n Program(node: any) {\n collectSolidDynamicImports(node, dynamicImports)\n },\n JSXOpeningElement(node: any) {\n for (const element of jsxElementNames(node, dynamicImports, context)) {\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) continue\n emit(node, element)\n }\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the published primitive that owns each role. Native anchors are not generic\n * elements, so their built-in link semantics are unaffected by the `link` entry.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n link: { primitive: 'TextLink', path: '@adea-ai/ui/components/ui/text-link' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroupItem', path: '@adea-ai/ui/components/ui/radio-group' },\n radiogroup: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'TabsTrigger', path: '@adea-ai/ui/components/ui/tabs' },\n tablist: { primitive: 'TabsList', path: '@adea-ai/ui/components/ui/tabs' },\n tabpanel: { primitive: 'TabsContent', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'SelectItem', path: '@adea-ai/ui/components/ui/select' },\n listbox: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n combobox: { primitive: 'Combobox', path: '@adea-ai/ui/components/ui/combobox' },\n searchbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n menu: { primitive: 'DropdownMenuContent', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n menuitem: { primitive: 'DropdownMenuItem', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n menuitemcheckbox: {\n primitive: 'DropdownMenuCheckboxItem',\n path: '@adea-ai/ui/components/ui/dropdown-menu',\n },\n menuitemradio: {\n primitive: 'DropdownMenuRadioItem',\n path: '@adea-ai/ui/components/ui/dropdown-menu',\n },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n tree: { primitive: 'Tree', path: '@adea-ai/ui/components/composites/tree' },\n treeitem: { primitive: 'TreeRow', path: '@adea-ai/ui/components/composites/tree' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set(\n [\n 'onClick',\n 'onDblClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'onTouchStart',\n 'onTouchEnd',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n 'on:touchstart',\n 'on:touchend',\n ].flatMap((handler) => [handler, handler.toLowerCase()])\n)\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Also reject opaque generic-element props and Solid Dynamic tag overrides. */\n rejectUnknownSpreads?: boolean\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Returns a const binding's immutable initializer, when its value is statically knowable. */\nconst constInitializer = (node: any, context: LintContext): any => {\n const variable = variableOf(node, context)\n const definition = variable?.defs?.[0]\n if (definition?.type !== 'Variable' || definition.parent?.kind !== 'const') return null\n return definition.node?.init ?? null\n}\n\n/** Reads literal strings through static branches and local const aliases. */\nconst staticStrings = (node: any, context?: LintContext, seen = new Set<unknown>()): string[] => {\n if (!node || seen.has(node)) return []\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return staticStrings(node.expression, context, seen)\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression, context, seen)\n if (node?.type === 'ConditionalExpression')\n return [\n ...staticStrings(node.consequent, context, new Set(seen)),\n ...staticStrings(node.alternate, context, new Set(seen)),\n ]\n if (node?.type === 'LogicalExpression')\n return [\n ...staticStrings(node.left, context, new Set(seen)),\n ...staticStrings(node.right, context, new Set(seen)),\n ]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n if (node?.type === 'Identifier' && context) {\n const initializer = constInitializer(node, context)\n return initializer ? staticStrings(initializer, context, seen) : []\n }\n return []\n}\n\ninterface SolidDynamicImports {\n named: WeakSet<object>\n namespaces: WeakSet<object>\n}\n\nconst collectSolidDynamicImport = (node: any, imports: SolidDynamicImports): void => {\n if (node.source?.value !== 'solid-js/web') return\n for (const specifier of node.specifiers ?? []) {\n if (specifier.type === 'ImportNamespaceSpecifier' && specifier.local)\n imports.namespaces.add(specifier.local)\n if (specifier.type !== 'ImportSpecifier') continue\n const imported = specifier.imported?.name ?? specifier.imported?.value\n if (imported === 'Dynamic' && specifier.local) imports.named.add(specifier.local)\n }\n}\n\nconst collectSolidDynamicImports = (program: any, imports: SolidDynamicImports): void => {\n for (const statement of program.body ?? [])\n if (statement.type === 'ImportDeclaration') collectSolidDynamicImport(statement, imports)\n}\n\nconst isImportedBinding = (node: any, bindings: WeakSet<object>, context: LintContext): boolean =>\n variableOf(node, context)?.identifiers?.some((identifier: object) => bindings.has(identifier)) ??\n false\n\nconst isSolidDynamic = (name: any, imports: SolidDynamicImports, context: LintContext): boolean => {\n if (name?.type === 'JSXIdentifier') return isImportedBinding(name, imports.named, context)\n return (\n name?.type === 'JSXMemberExpression' &&\n name.property?.name === 'Dynamic' &&\n name.object?.type === 'JSXIdentifier' &&\n isImportedBinding(name.object, imports.namespaces, context)\n )\n}\n\ninterface DynamicComponentResolution {\n values: any[]\n opaqueAfterKnownIntrinsic: boolean\n}\n\nconst valuesIncludeKnownIntrinsic = (values: any[], context: LintContext): boolean =>\n values\n .flatMap((value) => staticStrings(value, context))\n .some((element) => GENERIC_ELEMENTS.has(element) || element in ELEMENT_PRIMITIVES)\n\n/** Applies `component` assignments made by a spread, tracking opaque overrides. */\nconst applyDynamicComponentSpread = (\n node: any,\n resolution: DynamicComponentResolution,\n context: LintContext\n): DynamicComponentResolution => {\n while (\n node?.type === 'TSAsExpression' ||\n node?.type === 'TSSatisfiesExpression' ||\n node?.type === 'TSNonNullExpression'\n )\n node = node.expression\n if (!node || node.type !== 'ObjectExpression')\n return {\n values: [],\n opaqueAfterKnownIntrinsic:\n resolution.opaqueAfterKnownIntrinsic ||\n valuesIncludeKnownIntrinsic(resolution.values, context),\n }\n\n let current = resolution\n for (const property of node.properties ?? []) {\n if (property.type === 'SpreadElement') {\n current = applyDynamicComponentSpread(property.argument, current, context)\n continue\n }\n if (property.type !== 'Property') continue\n const key = staticPropertyKey(property.key, property.computed === true)\n if (key === 'component')\n current = { values: [property.value], opaqueAfterKnownIntrinsic: false }\n else if (property.computed && key === null)\n current = {\n values: [],\n opaqueAfterKnownIntrinsic:\n current.opaqueAfterKnownIntrinsic || valuesIncludeKnownIntrinsic(current.values, context),\n }\n }\n return current\n}\n\nconst dynamicComponentResolution = (\n node: any,\n context: LintContext\n): DynamicComponentResolution => {\n let resolution: DynamicComponentResolution = {\n values: [],\n opaqueAfterKnownIntrinsic: false,\n }\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXAttribute' && attribute.name?.name === 'component') {\n resolution = {\n values: attribute.value ? [attribute.value] : [],\n opaqueAfterKnownIntrinsic: false,\n }\n continue\n }\n if (attribute.type === 'JSXSpreadAttribute')\n resolution = applyDynamicComponentSpread(attribute.argument, resolution, context)\n }\n return resolution\n}\n\n/** Resolves native tags passed to Solid Dynamic while leaving component values alone. */\nconst jsxElementNames = (\n node: any,\n imports: SolidDynamicImports,\n context: LintContext\n): string[] => {\n if (isSolidDynamic(node.name, imports, context))\n return [\n ...new Set(\n dynamicComponentResolution(node, context).values.flatMap((value) =>\n staticStrings(value, context)\n )\n ),\n ]\n if (node.name?.type === 'JSXIdentifier') return [node.name.name]\n return []\n}\n\nconst roleOf = (node: any, context: LintContext): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXSpreadAttribute') {\n const role = staticObjectPropertyValues(attribute.argument, 'role')\n .flatMap((value) => staticStrings(value, context))\n .flatMap((value) => value.split(/\\s+/))\n .find((value) => value in ROLE_PRIMITIVES)\n if (role) return role\n continue\n }\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value, context)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n (attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name) ||\n (attribute.type === 'JSXSpreadAttribute' && objectHasStaticProperty(attribute.argument, name))\n )\n\nconst tabStopValue = (value: any): boolean => {\n const expression = value?.type === 'JSXExpressionContainer' ? value.expression : value\n if (expression?.type === 'Literal') return !(Number(expression.value) < 0)\n if (\n expression?.type === 'UnaryExpression' &&\n expression.operator === '-' &&\n expression.argument?.type === 'Literal'\n )\n return !(Number(expression.argument.value) > 0)\n return true\n}\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type === 'JSXAttribute' &&\n ['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return tabStopValue(attribute.value)\n if (attribute.type === 'JSXSpreadAttribute')\n return ['tabIndex', 'tabindex'].some((name) =>\n staticObjectPropertyValues(attribute.argument, name).some(tabStopValue)\n )\n return false\n })\n\n/** Unknown/computed props make a JSX object spread opaque to interaction checks. */\nconst opaqueSpread = (node: any, seen = new Set<unknown>()): boolean => {\n while (\n node?.type === 'TSAsExpression' ||\n node?.type === 'TSSatisfiesExpression' ||\n node?.type === 'TSNonNullExpression'\n )\n node = node.expression\n if (!node || seen.has(node) || node.type !== 'ObjectExpression') return true\n seen.add(node)\n return node.properties.some((property: any) => {\n if (property.type === 'SpreadElement') return opaqueSpread(property.argument, seen)\n if (property.type !== 'Property') return true\n return staticPropertyKey(property.key, property.computed === true) === null\n })\n}\n\nconst hasOpaqueSpread = (node: any): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) => attribute.type === 'JSXSpreadAttribute' && opaqueSpread(attribute.argument)\n )\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n rejectUnknownSpreads: { type: 'boolean' },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n opaqueSpreadWrapper:\n 'A spread on {{element}} may hide a role, activation handler, or tab stop. Pass statically named props or compose the shared component instead.',\n opaqueDynamicComponentWrapper:\n 'An opaque spread follows a known raw design-system or generic tag on Solid Dynamic and may replace the tag or hide interactive props. Pass statically named props or a shared component so the interaction rule can inspect the final component.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n const dynamicImports: SolidDynamicImports = {\n named: new WeakSet(),\n namespaces: new WeakSet(),\n }\n return {\n Program(node: any) {\n collectSolidDynamicImports(node, dynamicImports)\n },\n JSXOpeningElement(node: any) {\n if (options.rejectUnknownSpreads && isSolidDynamic(node.name, dynamicImports, context)) {\n const resolution = dynamicComponentResolution(node, context)\n if (resolution.opaqueAfterKnownIntrinsic) {\n context.report({\n node,\n messageId: 'opaqueDynamicComponentWrapper',\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n }\n\n for (const element of jsxElementNames(node, dynamicImports, context)) {\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) continue\n // Capitalized JSX components are composed controls, not raw elements.\n // Solid Dynamic is resolved above only when its component is a known tag.\n if (element[0] === element[0]?.toUpperCase()) continue\n if (!GENERIC_ELEMENTS.has(element)) continue\n\n if (options.rejectUnknownSpreads && hasOpaqueSpread(node)) {\n context.report({\n node,\n messageId: 'opaqueSpreadWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n break\n }\n\n const role = roleOf(node, context)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n continue\n }\n\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n continue\n }\n if (hasTabStop(node))\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n },\n }\n },\n}\n\nconst BUTTON_MODULE = '@adea-ai/ui/components/ui/button'\nconst ACTION_BUTTON_MODULE = '@adea-ai/ui/components/composites/action-button'\nconst ICON_BUTTON_SIZES: ReadonlySet<string> = new Set([\n 'icon-2xs',\n 'icon-xs',\n 'icon-sm',\n 'icon-md',\n 'icon-lg',\n 'icon-xl',\n 'icon-2xl',\n])\n\nconst staticJsxValues = (value: any): any[] =>\n value?.type === 'JSXExpressionContainer' ? [value.expression] : value ? [value] : []\n\nconst attributeValues = (node: any, name: string): any[] => {\n const values: any[] = []\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXAttribute' && attribute.name?.name === name)\n values.splice(0, values.length, ...staticJsxValues(attribute.value))\n if (attribute.type === 'JSXSpreadAttribute') {\n const spreadValues = staticObjectPropertyValues(attribute.argument, name)\n if (spreadValues.length > 0) values.splice(0, values.length, spreadValues.at(-1))\n }\n }\n return values\n}\n\nconst staticStringValue = (node: any): string | null => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\nconst isIconButtonSize = (node: any): boolean =>\n attributeValues(node, 'size').some((value) => {\n const size = staticStringValue(value)\n return size !== null && ICON_BUTTON_SIZES.has(size)\n })\n\nconst actionTooltipStatus = (node: any): 'valid' | 'blank' | 'unknown' | 'missing' => {\n const values = attributeValues(node, 'tooltip')\n if (values.length === 0) return 'missing'\n const strings = values.map(staticStringValue)\n if (strings.some((value) => value !== null && value.trim().length > 0)) return 'valid'\n if (strings.every((value) => value !== null)) return 'blank'\n return 'unknown'\n}\n\nconst isImportedComponent = (identifier: any, imports: Set<string>): boolean =>\n identifier?.type === 'Identifier' && imports.has(identifier.name)\n\n/** Requires shared icon actions to carry the shared explanatory-tooltip contract. */\nconst requireActionButtonTooltip: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer icon-size shared Button actions use ActionButton with a nonblank tooltip; dynamic values remain a review boundary.',\n },\n schema: [],\n messages: {\n useActionButton:\n 'Icon-size Button actions use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.',\n missingTooltip:\n 'An icon-size ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well.',\n },\n },\n create(context) {\n const buttons = new Set<string>()\n const actionButtons = new Set<string>()\n const recordImports = (node: any) => {\n const source = node.source?.value\n const target =\n source === BUTTON_MODULE ? buttons : source === ACTION_BUTTON_MODULE ? actionButtons : null\n if (!target) return\n for (const specifier of node.specifiers ?? []) {\n if (specifier.type !== 'ImportSpecifier') continue\n const imported = specifier.imported?.name ?? specifier.imported?.value\n if (\n (source === BUTTON_MODULE && imported === 'Button') ||\n (source === ACTION_BUTTON_MODULE && imported === 'ActionButton')\n )\n target.add(specifier.local?.name)\n }\n }\n\n return {\n ImportDeclaration: recordImports,\n JSXOpeningElement(node: any) {\n if (!isIconButtonSize(node)) return\n const name = node.name?.type === 'JSXIdentifier' ? node.name.name : null\n const isButton = name !== null && buttons.has(name)\n const isActionButton = name !== null && actionButtons.has(name)\n const asValues = attributeValues(node, 'as')\n const targetsButton = asValues.some((value) => isImportedComponent(value, buttons))\n const targetsActionButton = asValues.some((value) =>\n isImportedComponent(value, actionButtons)\n )\n if (isButton || targetsButton) {\n context.report({ node, messageId: 'useActionButton' })\n return\n }\n if (!isActionButton && !targetsActionButton) return\n const status = actionTooltipStatus(node)\n if (status === 'missing' || status === 'blank')\n context.report({ node, messageId: 'missingTooltip' })\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-inline-styles': noInlineStyles,\n 'no-class-list': noClassList,\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n 'require-action-button-tooltip': requireActionButtonTooltip,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.95.2",
3
+ "version": "0.95.3",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/src/lint/index.ts CHANGED
@@ -42,6 +42,9 @@ type RuleModule = {
42
42
 
43
43
  interface LintContext {
44
44
  options?: unknown[]
45
+ sourceCode?: {
46
+ getScope?: (node: unknown) => LintScope | undefined
47
+ }
45
48
  report: (descriptor: {
46
49
  node: unknown
47
50
  messageId?: string
@@ -50,6 +53,25 @@ interface LintContext {
50
53
  }) => void
51
54
  }
52
55
 
56
+ interface LintScope {
57
+ set?: Map<string, any>
58
+ upper?: LintScope
59
+ }
60
+
61
+ interface LintVariable {
62
+ defs?: { type?: string; parent?: { kind?: string }; node?: { init?: unknown } }[]
63
+ identifiers?: object[]
64
+ }
65
+
66
+ const variableOf = (node: any, context: LintContext): LintVariable | null => {
67
+ if (node?.type !== 'Identifier' && node?.type !== 'JSXIdentifier') return null
68
+ for (let scope = context.sourceCode?.getScope?.(node); scope; scope = scope.upper) {
69
+ const variable = scope.set?.get(node.name)
70
+ if (variable) return variable
71
+ }
72
+ return null
73
+ }
74
+
53
75
  /** Reads a static object-property key without evaluating a computed expression. */
54
76
  const staticPropertyKey = (node: any, computed: boolean): string | null => {
55
77
  if (!computed && node?.type === 'Identifier') return node.name
@@ -177,7 +199,7 @@ const noRawInteractiveElements: RuleModule = {
177
199
  type: 'problem',
178
200
  docs: {
179
201
  description:
180
- 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',
202
+ 'Interactive elements this package provides (button, input, textarea, select, option, label), including statically known Solid Dynamic tags, are composed from the package, not written as raw markup.',
181
203
  },
182
204
  schema: [
183
205
  {
@@ -197,6 +219,10 @@ const noRawInteractiveElements: RuleModule = {
197
219
  create(context) {
198
220
  const options = (context.options?.[0] ?? {}) as RawElementOptions
199
221
  const allowed = new Set(options.allow ?? [])
222
+ const dynamicImports: SolidDynamicImports = {
223
+ named: new WeakSet(),
224
+ namespaces: new WeakSet(),
225
+ }
200
226
  const emit = (node: unknown, element: string) => {
201
227
  const target = ELEMENT_PRIMITIVES[element]!
202
228
  context.report({
@@ -207,11 +233,14 @@ const noRawInteractiveElements: RuleModule = {
207
233
  })
208
234
  }
209
235
  return {
236
+ Program(node: any) {
237
+ collectSolidDynamicImports(node, dynamicImports)
238
+ },
210
239
  JSXOpeningElement(node: any) {
211
- if (node.name?.type !== 'JSXIdentifier') return
212
- const element = node.name.name
213
- if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return
214
- emit(node, element)
240
+ for (const element of jsxElementNames(node, dynamicImports, context)) {
241
+ if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) continue
242
+ emit(node, element)
243
+ }
215
244
  },
216
245
  }
217
246
  },
@@ -238,20 +267,37 @@ const DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
238
267
 
239
268
  /**
240
269
  * The interactive ARIA roles an application can dress a generic element in, and
241
- * the primitive that owns each role. `link` is deliberately absent: an `<a>` is
242
- * natively interactive and Button itself supports `as="a"` for the link-styled
243
- * case.
270
+ * the published primitive that owns each role. Native anchors are not generic
271
+ * elements, so their built-in link semantics are unaffected by the `link` entry.
244
272
  */
245
273
  const ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =
246
274
  Object.freeze({
247
275
  button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },
276
+ link: { primitive: 'TextLink', path: '@adea-ai/ui/components/ui/text-link' },
248
277
  checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },
249
- radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },
278
+ radio: { primitive: 'RadioGroupItem', path: '@adea-ai/ui/components/ui/radio-group' },
279
+ radiogroup: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },
250
280
  switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },
251
- tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },
252
- option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },
253
- menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },
281
+ tab: { primitive: 'TabsTrigger', path: '@adea-ai/ui/components/ui/tabs' },
282
+ tablist: { primitive: 'TabsList', path: '@adea-ai/ui/components/ui/tabs' },
283
+ tabpanel: { primitive: 'TabsContent', path: '@adea-ai/ui/components/ui/tabs' },
284
+ option: { primitive: 'SelectItem', path: '@adea-ai/ui/components/ui/select' },
285
+ listbox: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },
286
+ combobox: { primitive: 'Combobox', path: '@adea-ai/ui/components/ui/combobox' },
287
+ searchbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },
288
+ menu: { primitive: 'DropdownMenuContent', path: '@adea-ai/ui/components/ui/dropdown-menu' },
289
+ menuitem: { primitive: 'DropdownMenuItem', path: '@adea-ai/ui/components/ui/dropdown-menu' },
290
+ menuitemcheckbox: {
291
+ primitive: 'DropdownMenuCheckboxItem',
292
+ path: '@adea-ai/ui/components/ui/dropdown-menu',
293
+ },
294
+ menuitemradio: {
295
+ primitive: 'DropdownMenuRadioItem',
296
+ path: '@adea-ai/ui/components/ui/dropdown-menu',
297
+ },
254
298
  textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },
299
+ tree: { primitive: 'Tree', path: '@adea-ai/ui/components/composites/tree' },
300
+ treeitem: { primitive: 'TreeRow', path: '@adea-ai/ui/components/composites/tree' },
255
301
  })
256
302
 
257
303
  /**
@@ -284,44 +330,206 @@ const GENERIC_ELEMENTS: ReadonlySet<string> = new Set([
284
330
  'svg',
285
331
  ])
286
332
 
287
- const CLICK_HANDLERS: ReadonlySet<string> = new Set([
288
- 'onClick',
289
- 'onDoubleClick',
290
- 'onPointerDown',
291
- 'onPointerUp',
292
- 'onMouseDown',
293
- 'onMouseUp',
294
- 'on:click',
295
- 'on:dblclick',
296
- 'on:pointerdown',
297
- 'on:pointerup',
298
- 'on:mousedown',
299
- 'on:mouseup',
300
- ])
333
+ const CLICK_HANDLERS: ReadonlySet<string> = new Set(
334
+ [
335
+ 'onClick',
336
+ 'onDblClick',
337
+ 'onDoubleClick',
338
+ 'onPointerDown',
339
+ 'onPointerUp',
340
+ 'onMouseDown',
341
+ 'onMouseUp',
342
+ 'onTouchStart',
343
+ 'onTouchEnd',
344
+ 'on:click',
345
+ 'on:dblclick',
346
+ 'on:pointerdown',
347
+ 'on:pointerup',
348
+ 'on:mousedown',
349
+ 'on:mouseup',
350
+ 'on:touchstart',
351
+ 'on:touchend',
352
+ ].flatMap((handler) => [handler, handler.toLowerCase()])
353
+ )
301
354
 
302
355
  interface InteractiveWrapperOptions {
303
356
  /** Element names this rule should not report (rare; prefer fixing the file). */
304
357
  allow?: string[]
358
+ /** Also reject opaque generic-element props and Solid Dynamic tag overrides. */
359
+ rejectUnknownSpreads?: boolean
305
360
  /** Replaces the default report message. */
306
361
  message?: string
307
362
  }
308
363
 
309
- /** Only statically knowable branches; unresolved roles remain a review boundary. */
310
- const staticStrings = (node: any): string[] => {
364
+ /** Returns a const binding's immutable initializer, when its value is statically knowable. */
365
+ const constInitializer = (node: any, context: LintContext): any => {
366
+ const variable = variableOf(node, context)
367
+ const definition = variable?.defs?.[0]
368
+ if (definition?.type !== 'Variable' || definition.parent?.kind !== 'const') return null
369
+ return definition.node?.init ?? null
370
+ }
371
+
372
+ /** Reads literal strings through static branches and local const aliases. */
373
+ const staticStrings = (node: any, context?: LintContext, seen = new Set<unknown>()): string[] => {
374
+ if (!node || seen.has(node)) return []
375
+ seen.add(node)
376
+ if (
377
+ node.type === 'TSAsExpression' ||
378
+ node.type === 'TSSatisfiesExpression' ||
379
+ node.type === 'TSNonNullExpression'
380
+ )
381
+ return staticStrings(node.expression, context, seen)
311
382
  if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]
312
- if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)
383
+ if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression, context, seen)
313
384
  if (node?.type === 'ConditionalExpression')
314
- return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]
385
+ return [
386
+ ...staticStrings(node.consequent, context, new Set(seen)),
387
+ ...staticStrings(node.alternate, context, new Set(seen)),
388
+ ]
389
+ if (node?.type === 'LogicalExpression')
390
+ return [
391
+ ...staticStrings(node.left, context, new Set(seen)),
392
+ ...staticStrings(node.right, context, new Set(seen)),
393
+ ]
315
394
  if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)
316
395
  return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]
396
+ if (node?.type === 'Identifier' && context) {
397
+ const initializer = constInitializer(node, context)
398
+ return initializer ? staticStrings(initializer, context, seen) : []
399
+ }
317
400
  return []
318
401
  }
319
402
 
320
- const roleOf = (node: any): string | null => {
403
+ interface SolidDynamicImports {
404
+ named: WeakSet<object>
405
+ namespaces: WeakSet<object>
406
+ }
407
+
408
+ const collectSolidDynamicImport = (node: any, imports: SolidDynamicImports): void => {
409
+ if (node.source?.value !== 'solid-js/web') return
410
+ for (const specifier of node.specifiers ?? []) {
411
+ if (specifier.type === 'ImportNamespaceSpecifier' && specifier.local)
412
+ imports.namespaces.add(specifier.local)
413
+ if (specifier.type !== 'ImportSpecifier') continue
414
+ const imported = specifier.imported?.name ?? specifier.imported?.value
415
+ if (imported === 'Dynamic' && specifier.local) imports.named.add(specifier.local)
416
+ }
417
+ }
418
+
419
+ const collectSolidDynamicImports = (program: any, imports: SolidDynamicImports): void => {
420
+ for (const statement of program.body ?? [])
421
+ if (statement.type === 'ImportDeclaration') collectSolidDynamicImport(statement, imports)
422
+ }
423
+
424
+ const isImportedBinding = (node: any, bindings: WeakSet<object>, context: LintContext): boolean =>
425
+ variableOf(node, context)?.identifiers?.some((identifier: object) => bindings.has(identifier)) ??
426
+ false
427
+
428
+ const isSolidDynamic = (name: any, imports: SolidDynamicImports, context: LintContext): boolean => {
429
+ if (name?.type === 'JSXIdentifier') return isImportedBinding(name, imports.named, context)
430
+ return (
431
+ name?.type === 'JSXMemberExpression' &&
432
+ name.property?.name === 'Dynamic' &&
433
+ name.object?.type === 'JSXIdentifier' &&
434
+ isImportedBinding(name.object, imports.namespaces, context)
435
+ )
436
+ }
437
+
438
+ interface DynamicComponentResolution {
439
+ values: any[]
440
+ opaqueAfterKnownIntrinsic: boolean
441
+ }
442
+
443
+ const valuesIncludeKnownIntrinsic = (values: any[], context: LintContext): boolean =>
444
+ values
445
+ .flatMap((value) => staticStrings(value, context))
446
+ .some((element) => GENERIC_ELEMENTS.has(element) || element in ELEMENT_PRIMITIVES)
447
+
448
+ /** Applies `component` assignments made by a spread, tracking opaque overrides. */
449
+ const applyDynamicComponentSpread = (
450
+ node: any,
451
+ resolution: DynamicComponentResolution,
452
+ context: LintContext
453
+ ): DynamicComponentResolution => {
454
+ while (
455
+ node?.type === 'TSAsExpression' ||
456
+ node?.type === 'TSSatisfiesExpression' ||
457
+ node?.type === 'TSNonNullExpression'
458
+ )
459
+ node = node.expression
460
+ if (!node || node.type !== 'ObjectExpression')
461
+ return {
462
+ values: [],
463
+ opaqueAfterKnownIntrinsic:
464
+ resolution.opaqueAfterKnownIntrinsic ||
465
+ valuesIncludeKnownIntrinsic(resolution.values, context),
466
+ }
467
+
468
+ let current = resolution
469
+ for (const property of node.properties ?? []) {
470
+ if (property.type === 'SpreadElement') {
471
+ current = applyDynamicComponentSpread(property.argument, current, context)
472
+ continue
473
+ }
474
+ if (property.type !== 'Property') continue
475
+ const key = staticPropertyKey(property.key, property.computed === true)
476
+ if (key === 'component')
477
+ current = { values: [property.value], opaqueAfterKnownIntrinsic: false }
478
+ else if (property.computed && key === null)
479
+ current = {
480
+ values: [],
481
+ opaqueAfterKnownIntrinsic:
482
+ current.opaqueAfterKnownIntrinsic || valuesIncludeKnownIntrinsic(current.values, context),
483
+ }
484
+ }
485
+ return current
486
+ }
487
+
488
+ const dynamicComponentResolution = (
489
+ node: any,
490
+ context: LintContext
491
+ ): DynamicComponentResolution => {
492
+ let resolution: DynamicComponentResolution = {
493
+ values: [],
494
+ opaqueAfterKnownIntrinsic: false,
495
+ }
496
+ for (const attribute of node.attributes ?? []) {
497
+ if (attribute.type === 'JSXAttribute' && attribute.name?.name === 'component') {
498
+ resolution = {
499
+ values: attribute.value ? [attribute.value] : [],
500
+ opaqueAfterKnownIntrinsic: false,
501
+ }
502
+ continue
503
+ }
504
+ if (attribute.type === 'JSXSpreadAttribute')
505
+ resolution = applyDynamicComponentSpread(attribute.argument, resolution, context)
506
+ }
507
+ return resolution
508
+ }
509
+
510
+ /** Resolves native tags passed to Solid Dynamic while leaving component values alone. */
511
+ const jsxElementNames = (
512
+ node: any,
513
+ imports: SolidDynamicImports,
514
+ context: LintContext
515
+ ): string[] => {
516
+ if (isSolidDynamic(node.name, imports, context))
517
+ return [
518
+ ...new Set(
519
+ dynamicComponentResolution(node, context).values.flatMap((value) =>
520
+ staticStrings(value, context)
521
+ )
522
+ ),
523
+ ]
524
+ if (node.name?.type === 'JSXIdentifier') return [node.name.name]
525
+ return []
526
+ }
527
+
528
+ const roleOf = (node: any, context: LintContext): string | null => {
321
529
  for (const attribute of node.attributes ?? []) {
322
530
  if (attribute.type === 'JSXSpreadAttribute') {
323
531
  const role = staticObjectPropertyValues(attribute.argument, 'role')
324
- .flatMap((value) => staticStrings(value))
532
+ .flatMap((value) => staticStrings(value, context))
325
533
  .flatMap((value) => value.split(/\s+/))
326
534
  .find((value) => value in ROLE_PRIMITIVES)
327
535
  if (role) return role
@@ -329,7 +537,7 @@ const roleOf = (node: any): string | null => {
329
537
  }
330
538
  if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue
331
539
  return (
332
- staticStrings(attribute.value)
540
+ staticStrings(attribute.value, context)
333
541
  .flatMap((role) => role.split(/\s+/))
334
542
  .find((role) => role in ROLE_PRIMITIVES) ?? null
335
543
  )
@@ -374,6 +582,28 @@ const hasTabStop = (node: any): boolean =>
374
582
  return false
375
583
  })
376
584
 
585
+ /** Unknown/computed props make a JSX object spread opaque to interaction checks. */
586
+ const opaqueSpread = (node: any, seen = new Set<unknown>()): boolean => {
587
+ while (
588
+ node?.type === 'TSAsExpression' ||
589
+ node?.type === 'TSSatisfiesExpression' ||
590
+ node?.type === 'TSNonNullExpression'
591
+ )
592
+ node = node.expression
593
+ if (!node || seen.has(node) || node.type !== 'ObjectExpression') return true
594
+ seen.add(node)
595
+ return node.properties.some((property: any) => {
596
+ if (property.type === 'SpreadElement') return opaqueSpread(property.argument, seen)
597
+ if (property.type !== 'Property') return true
598
+ return staticPropertyKey(property.key, property.computed === true) === null
599
+ })
600
+ }
601
+
602
+ const hasOpaqueSpread = (node: any): boolean =>
603
+ (node.attributes ?? []).some(
604
+ (attribute: any) => attribute.type === 'JSXSpreadAttribute' && opaqueSpread(attribute.argument)
605
+ )
606
+
377
607
  const noInteractiveWrappers: RuleModule = {
378
608
  meta: {
379
609
  type: 'problem',
@@ -386,6 +616,7 @@ const noInteractiveWrappers: RuleModule = {
386
616
  type: 'object',
387
617
  properties: {
388
618
  allow: { type: 'array', items: { type: 'string' } },
619
+ rejectUnknownSpreads: { type: 'boolean' },
389
620
  message: { type: 'string' },
390
621
  },
391
622
  additionalProperties: false,
@@ -398,34 +629,65 @@ const noInteractiveWrappers: RuleModule = {
398
629
  'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',
399
630
  tabindexWrapper:
400
631
  'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',
632
+ opaqueSpreadWrapper:
633
+ 'A spread on {{element}} may hide a role, activation handler, or tab stop. Pass statically named props or compose the shared component instead.',
634
+ opaqueDynamicComponentWrapper:
635
+ 'An opaque spread follows a known raw design-system or generic tag on Solid Dynamic and may replace the tag or hide interactive props. Pass statically named props or a shared component so the interaction rule can inspect the final component.',
401
636
  },
402
637
  },
403
638
  create(context) {
404
639
  const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions
405
640
  const allowed = new Set(options.allow ?? [])
641
+ const dynamicImports: SolidDynamicImports = {
642
+ named: new WeakSet(),
643
+ namespaces: new WeakSet(),
644
+ }
406
645
  return {
646
+ Program(node: any) {
647
+ collectSolidDynamicImports(node, dynamicImports)
648
+ },
407
649
  JSXOpeningElement(node: any) {
408
- if (node.name?.type !== 'JSXIdentifier') return
409
- const element = node.name.name
410
- if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return
411
- // A capitalized element is a component — often a design-system primitive
412
- // composing a role deliberately (a Button carrying role="option" inside a
413
- // custom listbox, for example). That is composition, not impersonation.
414
- if (element[0] === element[0]?.toUpperCase()) return
415
-
416
- const role = roleOf(node)
417
- if (role && role in ROLE_PRIMITIVES) {
418
- const target = ROLE_PRIMITIVES[role]!
419
- context.report({
420
- node,
421
- messageId: 'roleWrapper',
422
- data: { element, role, primitive: target.primitive, path: target.path },
423
- ...(options.message ? { message: options.message } : {}),
424
- })
425
- return
650
+ if (options.rejectUnknownSpreads && isSolidDynamic(node.name, dynamicImports, context)) {
651
+ const resolution = dynamicComponentResolution(node, context)
652
+ if (resolution.opaqueAfterKnownIntrinsic) {
653
+ context.report({
654
+ node,
655
+ messageId: 'opaqueDynamicComponentWrapper',
656
+ ...(options.message ? { message: options.message } : {}),
657
+ })
658
+ return
659
+ }
426
660
  }
427
661
 
428
- if (GENERIC_ELEMENTS.has(element)) {
662
+ for (const element of jsxElementNames(node, dynamicImports, context)) {
663
+ if (allowed.has(element) || element in ELEMENT_PRIMITIVES) continue
664
+ // Capitalized JSX components are composed controls, not raw elements.
665
+ // Solid Dynamic is resolved above only when its component is a known tag.
666
+ if (element[0] === element[0]?.toUpperCase()) continue
667
+ if (!GENERIC_ELEMENTS.has(element)) continue
668
+
669
+ if (options.rejectUnknownSpreads && hasOpaqueSpread(node)) {
670
+ context.report({
671
+ node,
672
+ messageId: 'opaqueSpreadWrapper',
673
+ data: { element },
674
+ ...(options.message ? { message: options.message } : {}),
675
+ })
676
+ break
677
+ }
678
+
679
+ const role = roleOf(node, context)
680
+ if (role && role in ROLE_PRIMITIVES) {
681
+ const target = ROLE_PRIMITIVES[role]!
682
+ context.report({
683
+ node,
684
+ messageId: 'roleWrapper',
685
+ data: { element, role, primitive: target.primitive, path: target.path },
686
+ ...(options.message ? { message: options.message } : {}),
687
+ })
688
+ continue
689
+ }
690
+
429
691
  const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))
430
692
  if (click) {
431
693
  const target = ROLE_PRIMITIVES.button!
@@ -435,16 +697,15 @@ const noInteractiveWrappers: RuleModule = {
435
697
  data: { element, primitive: target.primitive, path: target.path },
436
698
  ...(options.message ? { message: options.message } : {}),
437
699
  })
438
- return
700
+ continue
439
701
  }
440
- if (hasTabStop(node)) {
702
+ if (hasTabStop(node))
441
703
  context.report({
442
704
  node,
443
705
  messageId: 'tabindexWrapper',
444
706
  data: { element },
445
707
  ...(options.message ? { message: options.message } : {}),
446
708
  })
447
- }
448
709
  }
449
710
  },
450
711
  }