@wix/origami-transforms 0.1.54 → 0.1.56
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.
- package/dist/jsxPlugins/validation/checks.d.ts +2 -1
- package/dist/jsxPlugins/validation/index.d.ts +1 -1
- package/dist/jsxPlugins/validation/providers.d.ts +1 -0
- package/dist/jsxPlugins/validation/tree.d.ts +1 -0
- package/package.json +3 -3
- package/src/jsxPlugins/validation/checks.js +6 -4
- package/src/jsxPlugins/validation/index.js +8 -4
- package/src/jsxPlugins/validation/providers.js +57 -0
- package/src/jsxPlugins/validation/tree.js +27 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export function assertKnownConstraints(rules: any): void;
|
|
2
2
|
export function constrainsChildren(rule: any): boolean;
|
|
3
|
-
export function checkRule(rule: import("./rules.js").Rule, { node, componentOf, report }: {
|
|
3
|
+
export function checkRule(rule: import("./rules.js").Rule, { node, componentOf, isProvider, report }: {
|
|
4
4
|
node: any;
|
|
5
5
|
componentOf: (node: any) => string | undefined;
|
|
6
|
+
isProvider: (node: any) => boolean;
|
|
6
7
|
report: (check: import("./index.js").Check, message: string) => void;
|
|
7
8
|
}): void;
|
|
8
9
|
export function checkCreateElement(rule: import("./rules.js").Rule, { node, component, report }: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const checks: readonly ["section_wrong_child", "section_too_few_children", "section_too_many_children", "section_non_literal_children", "section_create_element"];
|
|
1
|
+
export const checks: readonly ["section_wrong_child", "section_too_few_children", "section_too_many_children", "section_non_literal_children", "section_create_element", "invalid_component_catalog"];
|
|
2
2
|
/** @typedef {typeof checks[number]} Check */
|
|
3
3
|
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
4
4
|
export const layoutValidationPlugin: import("../../transform/types.js").JsxTransformPlugin<Check>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function findProviderTags(program: any, root: string | undefined): Set<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-transforms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"description": "Babel and PostCSS checks and transforms for Wix origami page files.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@wix/origami-system-components": "1.10.14",
|
|
25
25
|
"postcss": "^8.5.3",
|
|
26
26
|
"postcss-custom-media": "^12.0.1",
|
|
27
|
-
"@wix/origami-bi": "0.
|
|
27
|
+
"@wix/origami-bi": "0.22.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^22.0.0",
|
|
@@ -76,5 +76,5 @@
|
|
|
76
76
|
},
|
|
77
77
|
"type": "module",
|
|
78
78
|
"types": "./dist/index.d.ts",
|
|
79
|
-
"falconPackageHash": "
|
|
79
|
+
"falconPackageHash": "db1e35b4c8643d29244f6bad8070b058b173b7fa7761c59210f8a8c2"
|
|
80
80
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// To add a kind of constraint: add a function to CHECKS, use its key in rules.js, and extend
|
|
6
6
|
// expectation() so messages state it.
|
|
7
7
|
|
|
8
|
-
import { describe } from './tree.js'
|
|
8
|
+
import { describe, elementUnderProviders } from './tree.js'
|
|
9
9
|
|
|
10
10
|
const CHILD_CONSTRAINTS = ['allowedChildren', 'minChildren', 'maxChildren']
|
|
11
11
|
|
|
@@ -86,17 +86,19 @@ export const assertKnownConstraints = rules => {
|
|
|
86
86
|
|
|
87
87
|
export const constrainsChildren = rule => CHILD_CONSTRAINTS.some(key => key in rule)
|
|
88
88
|
|
|
89
|
-
/** Runs every constraint of `rule` on one element; `report` receives each full message.
|
|
89
|
+
/** Runs every constraint of `rule` on one element; `report` receives each full message. A child counts as the element
|
|
90
|
+
* under its context providers.
|
|
90
91
|
* @param {import('./rules.js').Rule} rule
|
|
91
|
-
* @param {{ node: any, componentOf: (node: any) => string | undefined, report: (check: import('./index.js').Check, message: string) => void }} input
|
|
92
|
+
* @param {{ node: any, componentOf: (node: any) => string | undefined, isProvider: (node: any) => boolean, report: (check: import('./index.js').Check, message: string) => void }} input
|
|
92
93
|
*/
|
|
93
|
-
export const checkRule = (rule, { node, componentOf, report }) => {
|
|
94
|
+
export const checkRule = (rule, { node, componentOf, isProvider, report }) => {
|
|
94
95
|
const prefix = `${describe(node)} ${expectation(rule)}`
|
|
95
96
|
/** @param {import('./index.js').Check} check */
|
|
96
97
|
const fail = check => got => report(check, `${prefix}; found ${got}.`)
|
|
97
98
|
|
|
98
99
|
const elements = node.children
|
|
99
100
|
.filter(child => child.type === 'JSXElement')
|
|
101
|
+
.map(child => elementUnderProviders(child, isProvider))
|
|
100
102
|
.map(element => ({ element, component: componentOf(element) }))
|
|
101
103
|
const context = { node, elements }
|
|
102
104
|
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
// One walk serves every rule. Rules are data (./rules.js); each names the components it looks at,
|
|
5
5
|
// identified for now by JSX tag name as written (<Section>, <InternalContainer>), whatever they are
|
|
6
|
-
// imported from, and the checks (./checks.js) run on each matching element.
|
|
6
|
+
// imported from, and the checks (./checks.js) run on each matching element. A context provider is the one exception: it's
|
|
7
|
+
// known by its import, against the site's component catalog (./providers.js), and a rule checks the element it wraps.
|
|
7
8
|
|
|
8
9
|
import { basename } from 'node:path'
|
|
9
10
|
import { LayoutError } from '../../transform/layoutError.js'
|
|
10
11
|
import { assertKnownConstraints, checkCreateElement, checkRule, constrainsChildren } from './checks.js'
|
|
12
|
+
import { findProviderTags } from './providers.js'
|
|
11
13
|
import { RULES } from './rules.js'
|
|
12
14
|
|
|
13
15
|
const STRUCTURE_FILES = new Set(['structure.tsx', 'structure.jsx'])
|
|
@@ -26,7 +28,7 @@ const isCreateElement = callee =>
|
|
|
26
28
|
(callee.type === 'Identifier' && callee.name === 'createElement') ||
|
|
27
29
|
(callee.type === 'MemberExpression' && !callee.computed && callee.property.name === 'createElement')
|
|
28
30
|
|
|
29
|
-
export const checks = /** @type {const} */ (['section_wrong_child', 'section_too_few_children', 'section_too_many_children', 'section_non_literal_children', 'section_create_element'])
|
|
31
|
+
export const checks = /** @type {const} */ (['section_wrong_child', 'section_too_few_children', 'section_too_many_children', 'section_non_literal_children', 'section_create_element', 'invalid_component_catalog'])
|
|
30
32
|
/** @typedef {typeof checks[number]} Check */
|
|
31
33
|
|
|
32
34
|
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
@@ -37,12 +39,14 @@ export const layoutValidationPlugin = {
|
|
|
37
39
|
order: -100,
|
|
38
40
|
appliesTo: context => STRUCTURE_FILES.has(basename(context.filePath)),
|
|
39
41
|
|
|
40
|
-
transform: program => {
|
|
42
|
+
transform: (program, context) => {
|
|
41
43
|
// The component an element is: its tag name, as written.
|
|
42
44
|
const componentOf = node => {
|
|
43
45
|
const name = node.openingElement.name
|
|
44
46
|
return name.type === 'JSXIdentifier' ? name.name : undefined
|
|
45
47
|
}
|
|
48
|
+
const providerTags = findProviderTags(program, context.layout?.root)
|
|
49
|
+
const isProvider = node => providerTags.has(componentOf(node))
|
|
46
50
|
|
|
47
51
|
/** @type {import('../../transform/types.js').Violation<Check>[]} */
|
|
48
52
|
const violations = []
|
|
@@ -53,7 +57,7 @@ export const layoutValidationPlugin = {
|
|
|
53
57
|
const component = componentOf(path.node)
|
|
54
58
|
const rules = component === undefined ? undefined : RULES_BY_COMPONENT.get(component)
|
|
55
59
|
if (rules === undefined) return
|
|
56
|
-
for (const rule of rules) checkRule(rule, { node: path.node, componentOf, report })
|
|
60
|
+
for (const rule of rules) checkRule(rule, { node: path.node, componentOf, isProvider, report })
|
|
57
61
|
},
|
|
58
62
|
|
|
59
63
|
CallExpression({ node }) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Which tags of a structure are context providers: a tag the structure imports from an app's wrapper package, which the
|
|
2
|
+
// site's component catalog lists among that app's context providers.
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { moduleFromWrapperEntry, wrapperImport } from '../../wrapperImport.js'
|
|
7
|
+
|
|
8
|
+
const CATALOG = 'site-component-catalog.json'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The site's component catalog; empty for a site exported before it.
|
|
12
|
+
* @param {string | undefined} root the site app's folder
|
|
13
|
+
* @returns {import('../../wrapperSideEffects.js').WrapperCatalog}
|
|
14
|
+
*/
|
|
15
|
+
const readCatalog = root => {
|
|
16
|
+
const file = root === undefined ? undefined : join(root, CATALOG)
|
|
17
|
+
if (file === undefined || !existsSync(file)) return {}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// The export's to fix, so it carries its check, as a layout violation does.
|
|
22
|
+
throw Object.assign(
|
|
23
|
+
new Error(`${CATALOG} is not valid JSON (${error instanceof Error ? error.message : String(error)}). Export the site again.`),
|
|
24
|
+
{ check: 'invalid_component_catalog' }
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The structure's imports of app wrappers, with the app each is from. */
|
|
30
|
+
const wrapperImportsOf = program =>
|
|
31
|
+
program.node.body
|
|
32
|
+
.filter(statement => statement.type === 'ImportDeclaration')
|
|
33
|
+
.map(statement => ({ statement, module: moduleFromWrapperEntry(statement.source.value) }))
|
|
34
|
+
.filter(({ module }) => module !== undefined)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The tags the structure names its context providers by. A structure that imports no app wrapper doesn't read the catalog.
|
|
38
|
+
* @param {any} program the structure file's Babel program path
|
|
39
|
+
* @param {string | undefined} root the site app's folder
|
|
40
|
+
* @returns {Set<string>}
|
|
41
|
+
*/
|
|
42
|
+
export const findProviderTags = (program, root) => {
|
|
43
|
+
const imports = wrapperImportsOf(program)
|
|
44
|
+
const catalog = imports.length === 0 ? {} : readCatalog(root)
|
|
45
|
+
/** @type {Set<string>} */
|
|
46
|
+
const tags = new Set()
|
|
47
|
+
for (const { statement, module } of imports) {
|
|
48
|
+
const providers = catalog[module]?.entries?.contextProviders ?? {}
|
|
49
|
+
for (const specifier of statement.specifiers) {
|
|
50
|
+
if (specifier.type !== 'ImportSpecifier') continue
|
|
51
|
+
const name = specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value
|
|
52
|
+
const isProvider = Object.hasOwn(providers, name) && wrapperImport({ module, symbol: name }) === statement.source.value
|
|
53
|
+
if (isProvider) tags.add(specifier.local.name)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return tags
|
|
57
|
+
}
|
|
@@ -13,3 +13,30 @@ export const describe = node => {
|
|
|
13
13
|
const line = node.loc ? ` (line ${node.loc.start.line})` : ''
|
|
14
14
|
return `<${tag}${id ? ` id="${id.value.value}"` : ''}>${line}`
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
// Whitespace between tags and {/* comments */}, which the converter skips too (parseJsx.ts isFillerChild).
|
|
18
|
+
const isFiller = child =>
|
|
19
|
+
(child.type === 'JSXText' && child.value.trim() === '') ||
|
|
20
|
+
(child.type === 'JSXExpressionContainer' && child.expression.type === 'JSXEmptyExpression')
|
|
21
|
+
|
|
22
|
+
/** The element's only child element; undefined when it has none, more, or anything else in it. */
|
|
23
|
+
const onlyChildElement = node => {
|
|
24
|
+
const children = node.children.filter(child => !isFiller(child))
|
|
25
|
+
return children.length === 1 && children[0].type === 'JSXElement' ? children[0] : undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The element a child stands for. A context provider wraps exactly one element, which stands in its place (the
|
|
30
|
+
* converter's parseJsx.ts wrappedChildOf), so the element under the providers; a provider that wraps none or more is
|
|
31
|
+
* itself the child, so the rule reports it.
|
|
32
|
+
* @param {any} element
|
|
33
|
+
* @param {(element: any) => boolean} isProvider
|
|
34
|
+
*/
|
|
35
|
+
export const elementUnderProviders = (element, isProvider) => {
|
|
36
|
+
while (isProvider(element)) {
|
|
37
|
+
const wrapped = onlyChildElement(element)
|
|
38
|
+
if (wrapped === undefined) break
|
|
39
|
+
element = wrapped
|
|
40
|
+
}
|
|
41
|
+
return element
|
|
42
|
+
}
|