@wix/origami-lint 1.34.31 → 1.34.32

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.
@@ -1,15 +1,18 @@
1
1
  // The "data-values" subject of the converter rules (converter 0.80 at c8ed79b): astValue.ts 99, 123, 133; parseJsx.ts 915, 1223,
2
2
  // 1227, 1231, 1235, 1255, 1268, 1289, 1697; projectData.ts 214, 225, 281, 435, 443; enrichImage.ts 94, 105.
3
3
  // Converter 0.81 (checked at 0.84, 2c0e21c): the tag's import, not a `type` attr, decides if a component takes data attributes; `type` is data.
4
+ // Tracker KIT-richText (converter 0.87): a rich text's type is Builder.RichText.
5
+ // Tracker OUR-003 (origami-transforms mediaValidation): an <Image>'s literal image uri is a Wix media id.
6
+ // Converter 0.87 (68c71e4): `children` is a data key.
4
7
  import { isDeepStrictEqual } from 'node:util';
5
8
  import { DATA_KEY, EVENT_ATTRIBUTE, RESERVED_ATTRIBUTES, RESERVED_ENTRY_KEYS, bare, getAttribute, getExpression, getKey, getName, getObject, getProperties, getCallbackKind, getProperty, isBound, isOneEditAway, isReadByImport, isReservedAttribute, ownerOf, } from "../../rules-api/index.js";
6
9
  import { defineRule } from "./define-rule.js";
7
- const CHECKS = ['attribute_without_braces', 'non_literal_data', 'duplicate_element_prop', 'invalid_element_props_object', 'invalid_element_props_entry', 'invalid_data_object', 'invalid_element_props_key', 'unsupported_element_props_key', 'mixed_data_wrapper', 'duplicate_data_key', 'bare_document_href', 'missing_document_id', 'inconsistent_image_url', 'misplaced_image_url_call'];
10
+ const CHECKS = ['attribute_without_braces', 'non_literal_data', 'duplicate_element_prop', 'invalid_element_props_object', 'invalid_element_props_entry', 'invalid_data_object', 'invalid_element_props_key', 'unsupported_element_props_key', 'mixed_data_wrapper', 'duplicate_data_key', 'bare_document_href', 'missing_document_id', 'inconsistent_image_url', 'misplaced_image_url_call', 'invalid_rich_text_type', 'invalid_image_media_id'];
8
11
  /** The names the import never reads as data (dataKeys.ts). The React name is joined as the converter does: Wix Standards fails the build on it in any file. */
9
12
  const EXCLUDED_DATA_KEYS = [
10
13
  '_slotsId', '_mapId', 'hidden', 'class', 'title', 'role', 'tabindex', 'draggable', 'lang', 'dir', 'accesskey', 'contenteditable', 'spellcheck',
11
14
  'translate', 'autofocus', 'inputmode', 'enterkeyhint', 'popover', 'inert', 'part', 'nonce', 'is', 'itemscope', 'itemtype', 'itemprop', 'itemid',
12
- 'itemref', 'tabIndex', 'contentEditable', 'accessKey', 'autoFocus', 'inputMode', 'enterKeyHint', 'spellCheck', 'children', 'styleRef', 'key',
15
+ 'itemref', 'tabIndex', 'contentEditable', 'accessKey', 'autoFocus', 'inputMode', 'enterKeyHint', 'spellCheck', 'styleRef', 'key',
13
16
  'ref', ['dangerouslySet', 'InnerHTML'].join(''), '__proto__', 'constructor', 'prototype',
14
17
  ];
15
18
  /** An elementProps entry's name is a CSS identifier (parseJsx.ts 1231): it becomes a class. */
@@ -18,9 +21,14 @@ const CSS_IDENTIFIER = /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/;
18
21
  const RESERVED_OBJECT_KEYS = ['__proto__', 'constructor', 'prototype'];
19
22
  /** An image object's `type` (enrichImage.ts). */
20
23
  const IMAGE_TYPE = 'Builder.Image';
24
+ /** A rich text's `type`, and the key the export writes it under (projectData.ts rebuildRichText reads `html` only on this type). */
25
+ const RICH_TEXT_TYPE = 'Builder.RichText';
26
+ const RICH_TEXT_KEY = 'richText';
21
27
  /** The media identifiers the export writes: an image's (`wix:image://v1/<uri>/<name>#originWidth=…`), a document's (`wix:document://v1/ugd/<file>/<name>`). */
22
28
  const IMAGE_PREFIX = 'wix:image://v1/';
23
29
  const DOCUMENT_PREFIX = 'wix:document://v1/';
30
+ /** A Wix media id, `117a96_1cff23187eeb4050a2a691bcdcad6502~mv2.jpg`: an account prefix, an underscore and a file name (mediaValidation/index.js). */
31
+ const WIX_MEDIA_ID = /^[A-Za-z0-9]+_[A-Za-z0-9_~.-]+\.[A-Za-z0-9]+$/;
24
32
  /** The reserved attributes this rule reads as data. The strings are component-attributes', `className` and `bindings` the bindings rule's. */
25
33
  const READ_AS_DATA = ['data', 'props', 'elementProps'];
26
34
  const FULL = { menus: true, media: true, jsx: true };
@@ -41,8 +49,9 @@ export default defineRule({
41
49
  },
42
50
  });
43
51
  // ── the callback's steps ──
44
- /** A component written out under Structure: its data values, then what the import builds from the ones it read. */
52
+ /** Any <Image> in the file: its media id, as the build checks it. Then a component written out under Structure: its data values, then what the import builds from the ones it read. */
45
53
  function checkJSXElementDataValues(input) {
54
+ checkImageMediaId(input);
46
55
  if (!input.rulesApi.getComponent(input.node) || !isReadByImport(input.node))
47
56
  return;
48
57
  const elementProps = getAttribute(input.node, 'elementProps');
@@ -54,6 +63,7 @@ function checkJSXElementDataValues(input) {
54
63
  checkDataKeyOnce(input, read.has('data') ? checkDataCarrier(input) : null);
55
64
  checkDocumentLinks(input, read);
56
65
  checkImageUrls(input, read);
66
+ checkRichTextTypes(input, read);
57
67
  }
58
68
  // ── the steps ──
59
69
  const say = ({ report }, node, check, message) => report({ check, loc: node.loc, message });
@@ -253,8 +263,8 @@ function hasDocumentId(identifier) {
253
263
  function checkImageUrls(input, read) {
254
264
  const urls = new Set();
255
265
  const values = dataValues(input).filter(([, path]) => read.has(attributeOf(path)));
256
- for (const [value, path] of values) {
257
- for (const { node, path: at } of objectsIn(value, path)) {
266
+ for (const [value, path, key] of values) {
267
+ for (const { node, path: at } of objectsIn(value, path, key)) {
258
268
  if (valueOf(getProperty(node, 'type')?.value ?? null) !== IMAGE_TYPE)
259
269
  continue;
260
270
  const url = getProperty(node, 'url');
@@ -274,6 +284,32 @@ function checkImageUrls(input, read) {
274
284
  if (stray)
275
285
  say(input, stray, 'misplaced_image_url_call', `${owner(input)} has \`media.getCroppedImageUrl(…)\` outside an image's url. Write it only as the \`url\` of a \`{ type: 'Builder.Image', … }\` object.`);
276
286
  }
287
+ /** KIT-richText: with another `type`, or none, the import takes the rich text but keeps its text empty (live import, 2026-10-26). */
288
+ function checkRichTextTypes(input, read) {
289
+ const values = dataValues(input).filter(([, path]) => read.has(attributeOf(path)));
290
+ for (const [value, path, valueKey] of values) {
291
+ for (const { node, path: at, key } of objectsIn(value, path, valueKey)) {
292
+ if (key !== RICH_TEXT_KEY)
293
+ continue;
294
+ const type = getProperty(node, 'type');
295
+ if (type && valueOf(type.value) === RICH_TEXT_TYPE)
296
+ continue;
297
+ const written = type ? `has \`type: ${textOf(input)(type.value)}\`` : 'has no `type`';
298
+ say(input, type ?? node, 'invalid_rich_text_type', `${owner(input)}'s \`${at}\` ${written}. Write \`type: '${RICH_TEXT_TYPE}'\`: with another type, or none, the import keeps its text empty (tracker KIT-richText).`);
299
+ }
300
+ }
301
+ }
302
+ /** OUR-003: `yarn build` fails on an <Image> (by tag name, anywhere in the file) whose literal `image.uri` isn't a Wix media id; the import takes it, but the image is broken. */
303
+ function checkImageMediaId(input) {
304
+ const image = getObject(getExpression(getAttribute(input.node, 'image')));
305
+ if (input.rulesApi.getTagName(input.node) !== 'Image' || !image)
306
+ return;
307
+ const uri = getProperty(image, 'uri');
308
+ const value = uri && bare(uri.value);
309
+ if (value?.type !== 'Literal' || typeof value.value !== 'string' || WIX_MEDIA_ID.test(value.value))
310
+ return;
311
+ say(input, value, 'invalid_image_media_id', `${owner(input)}'s \`image.uri\` '${value.value}' isn't a Wix media id, and \`yarn build\` fails on it. Run \`wix origami upload-media\` to upload it (tracker OUR-003).`);
312
+ }
277
313
  function imageCallsIn(input, dataValuesRead, read) {
278
314
  const values = dataValuesRead.map(([value]) => value);
279
315
  const props = read.has('props') ? getExpression(getAttribute(input.node, 'props')) : null;
@@ -465,29 +501,30 @@ function derivedImageArguments(image) {
465
501
  ];
466
502
  }
467
503
  const sameArguments = (call, expected) => isDeepStrictEqual(call.arguments.map(valueOf), expected);
468
- function objectsIn(node, path) {
504
+ function objectsIn(node, path, key) {
469
505
  if (node === null)
470
506
  return [];
471
507
  const value = bare(node);
472
508
  if (value.type === 'ArrayExpression')
473
- return value.elements.flatMap((element, index) => (element && element.type !== 'SpreadElement' ? objectsIn(element, `${path}[${index}]`) : []));
509
+ return value.elements.flatMap((element, index) => (element && element.type !== 'SpreadElement' ? objectsIn(element, `${path}[${index}]`, null) : []));
474
510
  if (value.type !== 'ObjectExpression')
475
511
  return [];
476
- return [{ node: value, path }, ...getProperties(value).flatMap((property) => objectsIn(property.value, `${path}.${getKey(property)}`))];
512
+ return [{ node: value, path, key }, ...getProperties(value).flatMap((property) => objectsIn(property.value, `${path}.${getKey(property)}`, getKey(property)))];
477
513
  }
514
+ /** Each data value: the value, its path as a message writes it, and the key that holds it. */
478
515
  function dataValues(input) {
479
516
  const element = input.node;
480
- const values = dataAttributes(input).map((attribute) => [getExpression(attribute), getName(attribute)]).filter(([value]) => value);
517
+ const values = dataAttributes(input).map((attribute) => [getExpression(attribute), getName(attribute), getName(attribute)]).filter(([value]) => value);
481
518
  const data = getExpression(getAttribute(element, 'data'));
482
519
  if (data)
483
- values.push([data, 'data']);
520
+ values.push([data, 'data', 'data']);
484
521
  for (const { entry, path } of getEntries(getObject(getExpression(getAttribute(element, 'elementProps'))))) {
485
522
  const at = `elementProps.${path.join('.')}`;
486
523
  for (const property of entryDataKeys(entry))
487
- values.push([property.value, `${at}.${getKey(property)}`]);
524
+ values.push([property.value, `${at}.${getKey(property)}`, getKey(property)]);
488
525
  const entryData = getProperty(entry, 'data');
489
526
  if (entryData)
490
- values.push([entryData.value, `${at}.data`]);
527
+ values.push([entryData.value, `${at}.data`, 'data']);
491
528
  }
492
529
  return values;
493
530
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/origami-lint",
3
- "version": "1.34.31",
3
+ "version": "1.34.32",
4
4
  "description": "Offline lint for an exported Origami site: lints the pages that changed since the last passing run",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,5 +52,5 @@
52
52
  ]
53
53
  }
54
54
  },
55
- "falconPackageHash": "686d35f346108816f50b0d95576cb023e273a903c797136d43187f3e"
55
+ "falconPackageHash": "85567c0c24100afc6ec763f714be471d941b2f00b00ec2182eb63324"
56
56
  }