@wix/origami-lint 1.34.4 → 1.34.6

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,43 +1,13 @@
1
+ import { GLOBAL_SECTIONS, RESERVED_ATTRIBUTES, RUNTIME_PACKAGE, SHARED_COMP_ID, STRUCTURE, SYSTEM_PACKAGE, TRANSFORM_BUILDERS } from "./constants.js";
1
2
  import { GLOBAL_COMPONENTS, GLOBAL_MENUS } from "./site-files-tree-mapping.js";
2
- /** Any section: rules that don't care which one. */
3
- export const SECTIONS = ['section', 'header', 'footer'];
4
- /** A page's reference to a global component (`<HeaderSection_x sharedCompId="…" />`). */
5
- export const GLOBAL_SECTIONS = ['globalHeader', 'globalFooter', 'globalSection'];
6
- /** A tag that wraps one component and isn't one itself: `<Remove when={…}>`, `<Interaction interactKey="…">`. */
7
- export const WRAPPERS = ['remove', 'interaction'];
8
- /** The name a structure file exports its tree under: `export const Structure = () => (…)`. */
9
- export const STRUCTURE = 'Structure';
10
- /** The props a global component's Structure may take: its instance identity, `({ id, className })` (#300). */
11
- export const INSTANCE_PROPS = ['id', 'className'];
12
- const SYSTEM = '@wix/origami-system-components';
13
- /** The runtime package: the builders (`ctx`, `fn`…) and the `<Remove>` wrapper. */
14
- export const RUNTIME_PACKAGE = '@wix/origami-runtime';
15
- /** The runtime's builders: the converter's BINDINGS_BUILDER_ORDER without `Remove`, which is a tag (reservedAttributes.ts). */
16
- export const BUILDERS = [
17
- 'all', 'ctx', 'fn', 'is', 'raw', 'state',
18
- 'textEnum', 'phone', 'email', 'webUrl', 'hostname', 'regex', 'guid', 'direction', 'localDate', 'localTime', 'localDateTime', 'dateTime',
19
- ];
20
- /** The six builders no other package exports under these names; `email`, `regex`… may well come from a library for fn(). */
21
- export const RUNTIME_ONLY_BUILDERS = ['all', 'ctx', 'fn', 'is', 'raw', 'state'];
22
- /** The attributes the converter reads on any component as something other than its data (reservedAttributes.ts RESERVED_ATTRIBUTES, converter 0.81: `type` is data since). */
23
- export const RESERVED_ATTRIBUTES = [
24
- 'id', '_id', 'data', 'props', 'skin', 'styleId', 'className', 'documentType', 'style', 'slot', 'bindings', 'elementProps',
25
- ];
26
- /** The attribute of a page's reference to a global component (SHARED_COMP_ID_ATTR); on any other tag it's data. */
27
- export const SHARED_COMP_ID = 'sharedCompId';
28
- /** The keys of an elementProps entry the converter reads as something other than the part's data (RESERVED_INNER_ELEMENT_ENTRY_KEYS, converter 0.80). */
29
- export const RESERVED_ENTRY_KEYS = ['data', 'slots', 'elementProps', 'bindings', 'className'];
30
- /** A data key is a JavaScript name (dataKeys.ts). */
31
- export const DATA_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
32
- /** By import source; the wrappers also by the imported name, since their packages export other things (`Show`, `Interact`). */
33
3
  const TYPE_BY_IMPORT_SOURCE = [
34
- [new RegExp(`^${SYSTEM}/(?:layout/)?Page$`), 'page'],
35
- [new RegExp(`^${SYSTEM}/layout/Header$`), 'headerLandmark'],
36
- [new RegExp(`^${SYSTEM}/layout/Footer$`), 'footerLandmark'],
37
- [new RegExp(`^${SYSTEM}/(?:layout/)?Section$`), 'section'],
38
- [new RegExp(`^${SYSTEM}/(?:layout/)?HeaderSection$`), 'header'],
39
- [new RegExp(`^${SYSTEM}/(?:layout/)?FooterSection$`), 'footer'],
40
- [new RegExp(`^${SYSTEM}/InternalContainer$`), 'innerElement'],
4
+ [new RegExp(`^${SYSTEM_PACKAGE}/(?:layout/)?Page$`), 'page'],
5
+ [new RegExp(`^${SYSTEM_PACKAGE}/layout/Header$`), 'headerLandmark'],
6
+ [new RegExp(`^${SYSTEM_PACKAGE}/layout/Footer$`), 'footerLandmark'],
7
+ [new RegExp(`^${SYSTEM_PACKAGE}/(?:layout/)?Section$`), 'section'],
8
+ [new RegExp(`^${SYSTEM_PACKAGE}/(?:layout/)?HeaderSection$`), 'header'],
9
+ [new RegExp(`^${SYSTEM_PACKAGE}/(?:layout/)?FooterSection$`), 'footer'],
10
+ [new RegExp(`^${SYSTEM_PACKAGE}/InternalContainer$`), 'innerElement'],
41
11
  // A global component's folder is `<Type>_<id>`: HeaderSection_mb7ogqqn.
42
12
  [new RegExp(`/${GLOBAL_COMPONENTS}/(?:HeaderSection|Header)_[^/]+$`), 'globalHeader'],
43
13
  [new RegExp(`/${GLOBAL_COMPONENTS}/(?:FooterSection|Footer)_[^/]+$`), 'globalFooter'],
@@ -54,6 +24,8 @@ const KIND_BY_IMPORT_SOURCE = [
54
24
  [new RegExp(`^(\\.\\.?/)*${GLOBAL_MENUS}(/|$)`), 'menu'],
55
25
  [/^document$/, 'core'],
56
26
  [/^document\//, 'document'],
27
+ [/^@origami-wrappers\//, 'wrapper'],
28
+ [/^@site\/site-code\/[^/]+$/, 'siteCode'],
57
29
  ];
58
30
  /** <Band> with `import { Section as Band } from '@wix/origami-system-components/Section'` → 'section'. `<Band.Item>` is Band's. A wrapper is the named export: `<Show>` from the runtime is null. */
59
31
  export function getType(element, program) {
@@ -64,6 +36,15 @@ export function getType(element, program) {
64
36
  const [, type, imported] = entry;
65
37
  return imported === undefined || found.imported === imported ? type : null;
66
38
  }
39
+ /** The tags the import reads as the DM's own components ('document') or a global component's reference (identity.ts identityOfTag): no data attributes. */
40
+ const NO_DATA_TYPES = ['page', 'section', 'header', 'footer', 'headerLandmark', 'footerLandmark', ...GLOBAL_SECTIONS];
41
+ /** Converter 0.81: are the tag's attributes saved as its component's data? Yes on a builder component (its import gives a componentType):
42
+ * imported, not from `document` and not from a local file. No on the DM's own tags (`Page`, the sections) and a global component's reference. */
43
+ export function hasDataAttributes(element, program) {
44
+ const found = getImport(getTagName(element).split('.')[0], program);
45
+ const type = getType(element, program);
46
+ return found !== null && found.kind !== 'core' && !found.source.startsWith('.') && !(type && NO_DATA_TYPES.includes(type));
47
+ }
67
48
  /** What an import's source is: 'runtime' for @wix/origami-runtime, 'interact' for @wix/interact/…; null for anything else. */
68
49
  export const getImportKind = (source) => KIND_BY_IMPORT_SOURCE.find(([pattern]) => pattern.test(source))?.[1] ?? null;
69
50
  /** Where a name of the file comes from; null when no import brings it in. */
@@ -109,7 +90,6 @@ export function getSlot(element) {
109
90
  return getLiteralString(element.openingElement.attributes.find(isSlotAttribute)?.value);
110
91
  }
111
92
  /** The attributes that hold an element's layers: drawn behind it, and pinned to it. */
112
- export const LAYER_ATTRIBUTES = ['backgroundLayer', 'pinnedLayer'];
113
93
  /** The elements this one holds in its attributes (`content={<X />}`, or inside the attribute's object or array, `elementProps` too), each with the attribute it is in. Not its children, and not the elements inside those elements. */
114
94
  export function getAttributeElements(owner) {
115
95
  const elementsIn = (value) => {
@@ -142,6 +122,15 @@ export function getExpression(attribute) {
142
122
  return null;
143
123
  return bare(value.expression);
144
124
  }
125
+ /** The object written out in a value, in parentheses or in an attribute's `{…}`: `x={{ a: 1 }}` → `{ a: 1 }`; null for any other value. */
126
+ export function getObject(node) {
127
+ if (!node)
128
+ return null;
129
+ const value = node.type === 'JSXAttribute' ? getExpression(node) : bare(node);
130
+ return value?.type === 'ObjectExpression' ? value : null;
131
+ }
132
+ /** Not the component's data: the converter's reserved attributes, and `sharedCompId` (reservedAttributes.ts). */
133
+ export const isReservedAttribute = (name) => RESERVED_ATTRIBUTES.includes(name) || name === SHARED_COMP_ID;
145
134
  /** The node inside its parentheses: `((x))` → `x`. */
146
135
  export function bare(node) {
147
136
  return node.type === 'ParenthesizedExpression' ? bare(node.expression) : node;
@@ -162,6 +151,52 @@ export function isStructure(node) {
162
151
  return declarator?.type === 'VariableDeclarator' && declarator.id?.type === 'Identifier' && declarator.id.name === STRUCTURE
163
152
  && declarator.parent?.parent?.type === 'ExportNamedDeclaration' && declarator.parent.parent.parent?.type === 'Program';
164
153
  }
154
+ /** The node's parent, past parentheses, and its child on the way up: the node itself, or the parentheses around it. */
155
+ function getOuter(node) {
156
+ let child = node;
157
+ while (child.parent?.type === 'ParenthesizedExpression')
158
+ child = child.parent;
159
+ return [child.parent, child];
160
+ }
161
+ /** The callbacks whose parameters the import reads as bindings: 'template' for a slot's template, `renderItem={(item, index, length) => <X />}`, an attribute's
162
+ * arrow that returns one element (parseJsx.ts templateCallbackOf); 'transform' for `map(source, (item, index) => ({…}))` and `shape(source, (obj) => ({…}))`
163
+ * (astValue.ts transformCallbackOf). null for any other node. */
164
+ export function getCallbackKind(syntax) {
165
+ const node = syntax;
166
+ if (node.type !== 'ArrowFunctionExpression')
167
+ return null;
168
+ let body = node.body;
169
+ while (body?.type === 'ParenthesizedExpression')
170
+ body = body.expression;
171
+ const [outer, child] = getOuter(node);
172
+ if (body?.type === 'JSXElement' && outer?.type === 'JSXExpressionContainer' && outer.parent?.type === 'JSXAttribute')
173
+ return 'template';
174
+ const isTransform = outer?.type === 'CallExpression' && outer.callee?.type === 'Identifier' && TRANSFORM_BUILDERS.includes(outer.callee.name) && outer.arguments?.[1] === child;
175
+ return isTransform ? 'transform' : null;
176
+ }
177
+ /** A read of a callback's parameter, `item`, `item.name`, `item['link-1']`, which the import reads as a binding (astValue.ts templateReadOf):
178
+ * 'template' off a renderItem's parameter, 'transform' off a map() or shape() callback's. null for any other value, or inside any other function. */
179
+ export function getParamReadKind(syntax) {
180
+ const node = syntax;
181
+ let root = node;
182
+ while (root.type === 'MemberExpression' && root.object)
183
+ root = root.object;
184
+ if (root.type !== 'Identifier')
185
+ return null;
186
+ for (let up = node.parent; up && up.type !== 'Program'; up = up.parent) {
187
+ if (up.type !== 'ArrowFunctionExpression' && up.type !== 'FunctionExpression')
188
+ continue;
189
+ const kind = getCallbackKind(up);
190
+ if (!kind)
191
+ return null;
192
+ if (up.params?.some((param) => param.type === 'Identifier' && param.name === root.name))
193
+ return kind;
194
+ }
195
+ return null;
196
+ }
197
+ /** A call, or a read of a renderItem / map() parameter (`item.name`): the import reads it as a binding, not as data (parseJsx.ts takeBoundProp).
198
+ * Any call: the bindings rule reports one that isn't a builder. */
199
+ export const isBound = (value) => !!value && (value.type === 'CallExpression' || getParamReadKind(value) !== null);
165
200
  /** A component in a message: `<Image>` img, or `<Image>` when it has no id. */
166
201
  export function ownerOf(element) {
167
202
  const id = getId(element);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/origami-lint",
3
- "version": "1.34.4",
3
+ "version": "1.34.6",
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": {
@@ -51,5 +51,5 @@
51
51
  ]
52
52
  }
53
53
  },
54
- "falconPackageHash": "dae194e28b366afdeb725197b97b0f1659346d4dbe5cea8e1659dab4"
54
+ "falconPackageHash": "bb0df5db12e07d7822c9dfb05afde996c91aa84c7842621d1e8245f2"
55
55
  }