@wix/origami-lint 1.34.33 → 1.34.35

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,6 +1,6 @@
1
1
  import { describeAtRule, reportCssProblem } from "../../rules-api/index.js";
2
2
  import { defineRule } from "./define-rule.js";
3
- const CHECKS = ['malformed_custom_media', 'duplicate_breakpoint', 'unknown_breakpoint_name', 'changed_breakpoint_range', 'undeclared_breakpoint', 'unknown_breakpoint_range'];
3
+ const CHECKS = ['malformed_custom_media', 'duplicate_breakpoint', 'unknown_breakpoint_name', 'changed_breakpoint_range', 'unknown_breakpoint_range'];
4
4
  /** The converter's shape for a declaration's name: a dashed identifier. */
5
5
  const DASHED_NAME = /^--[A-Za-z0-9_-]+$/;
6
6
  export default defineRule({
@@ -63,27 +63,27 @@ function keepsSiteRange(file, declaration, site, report) {
63
63
  reportCssProblem(report, file.name, declaration.line, 'changed_breakpoint_range', `\`@custom-media ${declaration.name} ${declaration.query}\` changes the site's range for ${declaration.name}, which is \`${exported}\`. Restore it; a breakpoint's width is changed in the editor, not in CSS.`);
64
64
  return false;
65
65
  }
66
- /** Reports a `@media (--name)` with no declaration of that name in the same file (CONV-breakpoints.ts:57). A malformed declaration of the name (its own line) counts as the declaration, so the use gets no second line. */
66
+ /** Reports a `@media (--name)` whose name isn't one of the site's breakpoints (CONV-breakpoints.ts:150): the import reads the name from the site's
67
+ * breakpoints, so the file needs no `@custom-media` of its own. Nothing without a theme to compare with. A name the file declares is reported on its
68
+ * declaration (checkDeclarations), so the use gets no second line. */
67
69
  function checkNamedUses(file, site, report) {
70
+ if (!site.hasTheme)
71
+ return;
68
72
  const declared = new Set(file.declared.flatMap(({ name }) => [name, `--${name}`]));
69
73
  for (const { name, line } of file.used) {
70
- if (!name || declared.has(name))
74
+ if (!name || declared.has(name) || getSiteRange(name, site) !== undefined)
71
75
  continue;
72
- const range = getSiteRange(name, site);
73
- const fix = range
74
- ? `add \`@custom-media ${name} ${range};\` at the top of ${file.name}, or write the range itself, \`@media ${range}\``
75
- : `add \`@custom-media ${name} (<its range>);\` at the top of ${file.name} (the range the site gives it), or write the range itself`;
76
- reportCssProblem(report, file.name, line, 'undeclared_breakpoint', `\`@media (${name})\` uses a name the file doesn't declare: ${fix}.`);
76
+ reportCssProblem(report, file.name, line, 'unknown_breakpoint_name', `\`@media (${name})\` names a breakpoint the site doesn't have. Use one of the theme's breakpoints: ${describe(site.breakpoints)}.`);
77
77
  }
78
78
  }
79
- /** Reports each `@media` of a file whose range is none of the site's (P4-R01, CONV-breakpoints.ts:75); nothing without a theme to compare with. A name is read through the file's declaration when that declaration is the site's; otherwise the steps above report it. A component's own breakpoint (inside `@scope`) is compared once unmapped.json is read. */
79
+ /** Reports each `@media` of a file whose range is none of the site's (P4-R01, CONV-breakpoints.ts:75); nothing without a theme to compare with. A name is read as the site's range for it; an unknown name is checkNamedUses'. A component's own breakpoint (inside `@scope`) is compared once unmapped.json is read. */
80
80
  function checkRanges(file, site, report) {
81
81
  if (!site.hasTheme)
82
82
  return;
83
83
  for (const media of file.used) {
84
84
  if (media.scope !== null)
85
85
  continue;
86
- const query = resolve(media, file.declared, site);
86
+ const query = resolve(media, site);
87
87
  if (query === null || site.breakpoints.some((breakpoint) => canonical(breakpoint.query) === canonical(query)))
88
88
  continue;
89
89
  reportCssProblem(report, file.name, media.line, 'unknown_breakpoint_range', `\`${describeAtRule({ name: 'media', params: media.query })}\` is a breakpoint the site doesn't have. Write one of the theme's breakpoints: ${describe(site.breakpoints)}. The import refuses a value for a breakpoint the site doesn't have.`);
@@ -112,13 +112,11 @@ function getMedia(file, isPage, rulesApi) {
112
112
  const hasShape = ({ name, query }) => DASHED_NAME.test(name) && query !== '';
113
113
  /** The site's range for a name; undefined when the theme has no well-formed declaration of it. */
114
114
  const getSiteRange = (name, site) => site.breakpoints.find((breakpoint) => breakpoint.name === name)?.query;
115
- /** The range to compare: the @media's own; for `(--name)`, the range the file declares for it, when that declaration is the site's name with the site's range (otherwise the steps above report it); null when there is nothing to compare. */
116
- function resolve(media, declarations, site) {
115
+ /** The range to compare: the @media's own; for `(--name)`, the site's range for that name, as the import reads it (an unknown name is checkNamedUses'); null when there is nothing to compare. */
116
+ function resolve(media, site) {
117
117
  if (!media.name)
118
118
  return media.query;
119
- const declaration = declarations.find((entry) => entry.name === media.name && hasShape(entry));
120
- const exported = getSiteRange(media.name, site);
121
- return declaration && exported !== undefined && exported.trim() === declaration.query.trim() ? declaration.query : null;
119
+ return getSiteRange(media.name, site) ?? null;
122
120
  }
123
121
  /** How the import reads a range: `(max-width:1023px)` and `(max-width: 1023px)` are the same; anything else, a different case or other spaces included, is another query. */
124
122
  const canonical = (query) => query.trim().replace(/:\s+/g, ':');
@@ -148,7 +148,7 @@ function isOutsideInternalContainers({ node, rulesApi, report }) {
148
148
  return;
149
149
  report({ check: 'landmark_inside_container', loc: getLoc(node, rulesApi), message: `The ${kind} ${describeElement(node, rulesApi)} is inside the internal container ${describeElement(container, rulesApi)}. Move it out of every internal container: the header and footer sit directly in the page or the master page.` });
150
150
  }
151
- /** Reports a wrapper that doesn't hold exactly one element: none, two, text, or a {…} (a comment too) beside it (CONV-parseJsx.ts:1028). */
151
+ /** Reports a wrapper that doesn't hold exactly one element: none, two, text, or a {…} beside it; a comment is fine (CONV-parseJsx.ts:1028). */
152
152
  function holdsOneElement({ node, rulesApi, report }) {
153
153
  if (holdsOne(node))
154
154
  return;
@@ -161,13 +161,13 @@ function checkNoRemoveInRemove({ node, rulesApi, report }) {
161
161
  return;
162
162
  report({ check: 'nested_remove', loc: node.openingElement.name.loc, message: `<${rulesApi.getTagName(node)}> is inside another <Remove>. <Remove> wraps the component itself: merge the two conditions into one <Remove>.` });
163
163
  }
164
- /** Reports text, a {…} (a comment too), a fragment or a spread between a component's tags (CONV-parseJsx.ts:1668). The root steps report those in <Page> and its sections, except comments, which they don't see. */
164
+ /** Reports text, a {…}, a fragment or a spread between a component's tags (CONV-parseJsx.ts:1668). A comment is fine, as whitespace is: the import
165
+ * skips both (it reads a `@name` comment as the component's name). The root steps report the rest in <Page> and its sections. */
165
166
  function hasOnlyElements({ node, rulesApi, report }) {
166
- const commentsOnly = isRootTreeHolder(node, rulesApi);
167
+ if (isRootTreeHolder(node, rulesApi))
168
+ return;
167
169
  for (const child of node.children) {
168
- if (child.type === 'JSXElement' || (child.type === 'JSXText' && child.value.trim() === ''))
169
- continue;
170
- if (commentsOnly && !isComment(child))
170
+ if (child.type === 'JSXElement' || isFiller(child))
171
171
  continue;
172
172
  report({ check: 'non_element_child', loc: child.type === 'JSXText' ? getTextLoc(child) : child.loc, message: `${describeElement(node, rulesApi)} has text or {…} as a child. Put the value in an attribute: only elements go between the tags.` });
173
173
  }
@@ -283,13 +283,13 @@ function isRootTreeHolder(holder, rulesApi) {
283
283
  }
284
284
  /** Whether the element is one of the holder's children (through wrappers), not something passed in one of its attributes. */
285
285
  const isStructuralChild = (holder, element, rulesApi) => getStructuralChildren(holder, rulesApi).includes(element);
286
- /** A wrapper holds exactly one element: non-blank text, a {…} (a comment too), a fragment count as what it holds. */
286
+ /** A wrapper holds exactly one element: non-blank text, a {…} and a fragment count as what it holds; whitespace and comments don't. */
287
287
  const holdsOne = (wrapper) => {
288
- const inside = wrapper.children.filter((child) => child.type !== 'JSXText' || child.value.trim() !== '');
288
+ const inside = wrapper.children.filter((child) => !isFiller(child));
289
289
  return inside.length === 1 && inside[0].type === 'JSXElement';
290
290
  };
291
- /** A JSX comment: an expression container with nothing in it. */
292
- const isComment = (child) => child.type === 'JSXExpressionContainer' && child.expression.type === 'JSXEmptyExpression';
291
+ /** What the import skips between tags: whitespace, and a JSX comment (an expression container with nothing in it). */
292
+ const isFiller = (child) => (child.type === 'JSXText' && child.value.trim() === '') || (child.type === 'JSXExpressionContainer' && child.expression.type === 'JSXEmptyExpression');
293
293
  /** `backgroundLayer={[<Background … />]}`: an array literal whose every entry is an element. */
294
294
  function isArrayOfElements(attribute) {
295
295
  const { value } = attribute;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/origami-lint",
3
- "version": "1.34.33",
3
+ "version": "1.34.35",
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": {
@@ -25,7 +25,7 @@
25
25
  "oxc-parser": "^0.151.0",
26
26
  "oxlint": "^1.39.0",
27
27
  "postcss": "^8.5.3",
28
- "@wix/origami-bi": "0.13.0"
28
+ "@wix/origami-bi": "0.14.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.0.0",
@@ -52,5 +52,5 @@
52
52
  ]
53
53
  }
54
54
  },
55
- "falconPackageHash": "fa4525b9f8f5fa63ad7d72b383deab33b1a9c16033290d6381eb0ee9"
55
+ "falconPackageHash": "3167f248bc0c6e01893243829d375999c8ee55bc2d649ba40becbf55"
56
56
  }