@wix/origami-transforms 0.1.28 → 0.1.29
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/cssPlugins/componentPosition.d.ts +6 -2
- package/dist/cssPlugins/customEnums.d.ts +5 -2
- package/dist/cssPlugins/customMedia.d.ts +7 -2
- package/dist/cssPlugins/fonts.d.ts +6 -2
- package/dist/cssPlugins/index.d.ts +2 -2
- package/dist/cssPlugins/layout/index.d.ts +7 -2
- package/dist/cssPlugins/mixins.d.ts +7 -2
- package/dist/cssPlugins/presets.d.ts +6 -2
- package/dist/cssPlugins/shapeDividers.d.ts +6 -2
- package/dist/cssPlugins/spx/index.d.ts +7 -2
- package/dist/cssPlugins/stackChildren.d.ts +5 -2
- package/dist/cssPlugins/wixVectorUrls.d.ts +6 -2
- package/dist/jsxPlugins/index.d.ts +2 -2
- package/dist/jsxPlugins/mediaValidation/index.d.ts +5 -2
- package/dist/jsxPlugins/origamiCompatibility/index.d.ts +6 -2
- package/dist/jsxPlugins/pageSlots.d.ts +7 -6
- package/dist/jsxPlugins/validation/checks.d.ts +7 -7
- package/dist/jsxPlugins/validation/index.d.ts +5 -2
- package/dist/jsxPlugins/validation/rules.d.ts +25 -0
- package/dist/transform/bi.d.ts +9 -0
- package/dist/transform/layoutError.d.ts +7 -4
- package/dist/transform/pipeline.d.ts +2 -2
- package/dist/transform/types.d.ts +11 -11
- package/package.json +3 -3
- package/src/cssPlugins/componentPosition.js +5 -1
- package/src/cssPlugins/customEnums.js +17 -11
- package/src/cssPlugins/customMedia.js +10 -3
- package/src/cssPlugins/fonts.js +5 -1
- package/src/cssPlugins/index.js +1 -1
- package/src/cssPlugins/layout/index.js +8 -1
- package/src/cssPlugins/layout/parser.js +15 -14
- package/src/cssPlugins/mixins.js +13 -6
- package/src/cssPlugins/presets.js +5 -1
- package/src/cssPlugins/shapeDividers.js +5 -1
- package/src/cssPlugins/spx/calls.js +2 -1
- package/src/cssPlugins/spx/cascade.js +3 -2
- package/src/cssPlugins/spx/index.js +8 -1
- package/src/cssPlugins/stackChildren.js +6 -2
- package/src/cssPlugins/wixVectorUrls.js +5 -1
- package/src/jsxPlugins/index.js +1 -1
- package/src/jsxPlugins/mediaValidation/index.js +10 -5
- package/src/jsxPlugins/origamiCompatibility/index.js +5 -1
- package/src/jsxPlugins/pageSlots.js +6 -1
- package/src/jsxPlugins/validation/checks.js +15 -8
- package/src/jsxPlugins/validation/index.js +9 -3
- package/src/jsxPlugins/validation/rules.js +14 -0
- package/src/transform/bi.js +30 -0
- package/src/transform/css/runPlugins.js +5 -7
- package/src/transform/jsx/runPlugins.js +3 -6
- package/src/transform/layoutError.js +7 -3
- package/src/transform/pipeline.js +2 -2
- package/src/transform/types.js +28 -6
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/** @
|
|
2
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
3
|
+
export const componentPositionPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
4
|
+
export type Check = (typeof checks)[number];
|
|
5
|
+
declare const checks: readonly [];
|
|
6
|
+
export {};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export function removeCustomEnums(root: any): void;
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
export const checks: readonly ["duplicate_enum", "non_literal_enum_value", "unknown_enum_value", "enum_parent_selector_list"];
|
|
3
|
+
/** @typedef {typeof checks[number]} Check */
|
|
4
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
5
|
+
export const customEnumsPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
6
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export const checks: readonly ["unresolved_custom_media"];
|
|
2
|
+
export function fail<E extends Error>(check: Check, error: E): E & {
|
|
3
|
+
check: "unresolved_custom_media";
|
|
4
|
+
};
|
|
5
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
6
|
+
export const customMediaPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
7
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/** @
|
|
2
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
3
|
+
export const fontsPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
4
|
+
export type Check = (typeof checks)[number];
|
|
5
|
+
declare const checks: readonly [];
|
|
6
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** @type {readonly import('../transform/types.js').CssTransformPlugin[]} */
|
|
2
|
-
export const cssPlugins: readonly import("../transform/types.js").CssTransformPlugin[];
|
|
1
|
+
/** @type {readonly import('../transform/types.js').CssTransformPlugin<string>[]} */
|
|
2
|
+
export const cssPlugins: readonly import("../transform/types.js").CssTransformPlugin<string>[];
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export const checks: readonly ["unknown_target", "target_requires_component_id", "invalid_target_envelope", "target_not_site_layout", "duplicate_target", "target_requires_declarations", "duplicate_target_property", "property_wrong_target", "content_position_not_relative", "unsupported_overflow", "unsupported_content_alignment", "inconsistent_container_display", "duplicate_root_property", "property_requires_target"];
|
|
2
|
+
export function fail<E extends Error>(check: Check, error: E): E & {
|
|
3
|
+
check: "unknown_target" | "target_requires_component_id" | "invalid_target_envelope" | "target_not_site_layout" | "duplicate_target" | "target_requires_declarations" | "duplicate_target_property" | "property_wrong_target" | "content_position_not_relative" | "unsupported_overflow" | "unsupported_content_alignment" | "inconsistent_container_display" | "duplicate_root_property" | "property_requires_target";
|
|
4
|
+
};
|
|
5
|
+
/** @type {import('../../transform/types.js').CssTransformPlugin<Check>} */
|
|
6
|
+
export const layoutPlugin: import("../../transform/types.js").CssTransformPlugin<Check>;
|
|
7
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export const checks: readonly ["missing_mixin", "cyclic_apply", "expansion_depth_exceeded", "cloned_node_budget_exceeded", "cloned_css_budget_exceeded"];
|
|
2
|
+
export function fail<E extends Error>(check: Check, error: E): E & {
|
|
3
|
+
check: "missing_mixin" | "cyclic_apply" | "expansion_depth_exceeded" | "cloned_node_budget_exceeded" | "cloned_css_budget_exceeded";
|
|
4
|
+
};
|
|
5
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
6
|
+
export const mixinsPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
7
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/** @
|
|
2
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
3
|
+
export const presetsPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
4
|
+
export type Check = (typeof checks)[number];
|
|
5
|
+
declare const checks: readonly [];
|
|
6
|
+
export {};
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/** @
|
|
2
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
3
|
+
export const shapeDividersPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
4
|
+
export type Check = (typeof checks)[number];
|
|
5
|
+
declare const checks: readonly [];
|
|
6
|
+
export {};
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export const checks: readonly ["unsupported_spx_resolver", "invalid_spx_reference_width", "non_literal_spx_argument"];
|
|
2
|
+
export function fail<E extends Error>(check: Check, error: E): E & {
|
|
3
|
+
check: "unsupported_spx_resolver" | "invalid_spx_reference_width" | "non_literal_spx_argument";
|
|
4
|
+
};
|
|
5
|
+
/** @type {import('../../transform/types.js').CssTransformPlugin<Check>} */
|
|
6
|
+
export const spxPlugin: import("../../transform/types.js").CssTransformPlugin<Check>;
|
|
7
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export const checks: readonly ["child_not_in_stack"];
|
|
2
|
+
/** @typedef {typeof checks[number]} Check */
|
|
3
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
4
|
+
export const stackChildrenPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
5
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/** @
|
|
2
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
3
|
+
export const wixVectorUrlsPlugin: import("../transform/types.js").CssTransformPlugin<Check>;
|
|
4
|
+
export type Check = (typeof checks)[number];
|
|
5
|
+
declare const checks: readonly [];
|
|
6
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** @type {readonly import('../transform/types.js').JsxTransformPlugin[]} */
|
|
2
|
-
export const jsxPlugins: readonly import("../transform/types.js").JsxTransformPlugin[];
|
|
1
|
+
/** @type {readonly import('../transform/types.js').JsxTransformPlugin<string>[]} */
|
|
2
|
+
export const jsxPlugins: readonly import("../transform/types.js").JsxTransformPlugin<string>[];
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export const checks: readonly ["media_not_wix"];
|
|
2
|
+
/** @typedef {typeof checks[number]} Check */
|
|
3
|
+
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
4
|
+
export const mediaValidationPlugin: import("../../transform/types.js").JsxTransformPlugin<Check>;
|
|
5
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/** @
|
|
2
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
3
|
+
export const origamiCompatibilityPlugin: import("../../transform/types.js").JsxTransformPlugin<Check>;
|
|
4
|
+
export type Check = (typeof checks)[number];
|
|
5
|
+
declare const checks: readonly [];
|
|
6
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/** @typedef {typeof checks[number]} Check */
|
|
2
|
+
/** Transitional support for DM's literal Page children alongside authored slot props.
|
|
3
|
+
* @type {import('../transform/types.js').JsxTransformPlugin<Check>} */
|
|
4
|
+
export const pageSlotsPlugin: import("../transform/types.js").JsxTransformPlugin<Check>;
|
|
5
|
+
export type Check = (typeof checks)[number];
|
|
6
|
+
declare const checks: readonly [];
|
|
7
|
+
export {};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export function assertKnownConstraints(rules: any): void;
|
|
2
2
|
export function constrainsChildren(rule: any): boolean;
|
|
3
|
-
export function checkRule(rule:
|
|
3
|
+
export function checkRule(rule: import("./rules.js").Rule, { node, componentOf, report }: {
|
|
4
4
|
node: any;
|
|
5
|
-
componentOf: any;
|
|
6
|
-
report:
|
|
5
|
+
componentOf: (node: any) => string | undefined;
|
|
6
|
+
report: (check: import("./index.js").Check, message: string) => void;
|
|
7
7
|
}): void;
|
|
8
|
-
export function checkCreateElement(rule:
|
|
8
|
+
export function checkCreateElement(rule: import("./rules.js").Rule, { node, component, report }: {
|
|
9
9
|
node: any;
|
|
10
|
-
component:
|
|
11
|
-
report:
|
|
12
|
-
}):
|
|
10
|
+
component: string;
|
|
11
|
+
report: (check: import("./index.js").Check, message: string) => void;
|
|
12
|
+
}): void;
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export const checks: readonly ["section_wrong_child", "section_too_few_children", "section_too_many_children", "section_non_literal_children", "section_create_element"];
|
|
2
|
+
/** @typedef {typeof checks[number]} Check */
|
|
3
|
+
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
4
|
+
export const layoutValidationPlugin: import("../../transform/types.js").JsxTransformPlugin<Check>;
|
|
5
|
+
export type Check = (typeof checks)[number];
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} Rule
|
|
3
|
+
* @property {string} id
|
|
4
|
+
* @property {string[]} components
|
|
5
|
+
* @property {string[]} allowedChildren
|
|
6
|
+
* @property {number} minChildren
|
|
7
|
+
* @property {number} maxChildren
|
|
8
|
+
* @property {Record<'allowedChildren' | 'minChildren' | 'maxChildren' | 'literal' | 'createElement', import('./index.js').Check>} checks
|
|
9
|
+
*/
|
|
10
|
+
/** @satisfies {Rule[]} */
|
|
1
11
|
export const RULES: {
|
|
2
12
|
id: string;
|
|
13
|
+
checks: {
|
|
14
|
+
allowedChildren: "section_wrong_child";
|
|
15
|
+
minChildren: "section_too_few_children";
|
|
16
|
+
maxChildren: "section_too_many_children";
|
|
17
|
+
literal: "section_non_literal_children";
|
|
18
|
+
createElement: "section_create_element";
|
|
19
|
+
};
|
|
3
20
|
components: string[];
|
|
4
21
|
allowedChildren: string[];
|
|
5
22
|
minChildren: number;
|
|
6
23
|
maxChildren: number;
|
|
7
24
|
}[];
|
|
25
|
+
export type Rule = {
|
|
26
|
+
id: string;
|
|
27
|
+
components: string[];
|
|
28
|
+
allowedChildren: string[];
|
|
29
|
+
minChildren: number;
|
|
30
|
+
maxChildren: number;
|
|
31
|
+
checks: Record<"allowedChildren" | "minChildren" | "maxChildren" | "literal" | "createElement", import("./index.js").Check>;
|
|
32
|
+
};
|
package/dist/transform/bi.d.ts
CHANGED
|
@@ -2,3 +2,12 @@
|
|
|
2
2
|
export function countPlugin(context: any, duration: any): void;
|
|
3
3
|
/** @param {'malformed_custom_enum' | 'unmappable_preset' | 'svg_not_loaded' | 'media_not_wix'} key */
|
|
4
4
|
export function warn(key: "malformed_custom_enum" | "unmappable_preset" | "svg_not_loaded" | "media_not_wix"): void;
|
|
5
|
+
/**
|
|
6
|
+
* @template {string} Check
|
|
7
|
+
* @param {import('./types.js').TransformPluginBase<Check>} plugin
|
|
8
|
+
* @param {unknown} cause
|
|
9
|
+
* @returns {Check | undefined}
|
|
10
|
+
*/
|
|
11
|
+
export function declaredCheck<Check extends string>(plugin: import("./types.js").TransformPluginBase<Check>, cause: unknown): Check | undefined;
|
|
12
|
+
/** Finish a plugin failure, projecting only its declared keys into check children. */
|
|
13
|
+
export function recordPluginFailure(plugin: any, cause: any, pageId: any): void;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/** @template {string} Check */
|
|
2
|
+
export class LayoutError<Check extends string> extends Error {
|
|
2
3
|
/**
|
|
3
|
-
* @param {
|
|
4
|
+
* @param {Pick<import('./types.js').TransformPluginBase<Check>, 'checks'>} plugin
|
|
5
|
+
* @param {readonly import('./types.js').Violation<NoInfer<Check>>[]} violations
|
|
4
6
|
* @param {{ file?: string, line?: number, column?: number }} [location] The first violation's, which the
|
|
5
7
|
* bundler prints as the error's position (Vite's CSS stage reads `file`, `line` and `column`).
|
|
6
8
|
*/
|
|
7
|
-
constructor(violations: readonly
|
|
9
|
+
constructor(plugin: Pick<import("./types.js").TransformPluginBase<Check>, "checks">, violations: readonly import("./types.js").Violation<NoInfer<Check>>[], location?: {
|
|
8
10
|
file?: string;
|
|
9
11
|
line?: number;
|
|
10
12
|
column?: number;
|
|
11
13
|
});
|
|
12
14
|
error_name: string;
|
|
13
15
|
stack: string;
|
|
14
|
-
|
|
16
|
+
plugin: Pick<import("./types.js").TransformPluginBase<Check>, "checks">;
|
|
17
|
+
violations: readonly import("./types.js").Violation<NoInfer<Check>>[];
|
|
15
18
|
location: {
|
|
16
19
|
file?: string;
|
|
17
20
|
line?: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function createTransformPipeline({ layout, jsxPlugins, cssPlugins }: {
|
|
2
2
|
layout: import("./types.js").SiteLayout;
|
|
3
|
-
jsxPlugins?: readonly import("./types.js").JsxTransformPlugin[];
|
|
4
|
-
cssPlugins?: readonly import("./types.js").CssTransformPlugin[];
|
|
3
|
+
jsxPlugins?: readonly import("./types.js").JsxTransformPlugin<string>[];
|
|
4
|
+
cssPlugins?: readonly import("./types.js").CssTransformPlugin<string>[];
|
|
5
5
|
}): {
|
|
6
6
|
/** Whether any JSX transform selects this module (structure.jsx/.tsx by default).
|
|
7
7
|
* @param {string} id
|
|
@@ -32,15 +32,15 @@ export type TransformContext = {
|
|
|
32
32
|
*/
|
|
33
33
|
ssr: boolean;
|
|
34
34
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
export type Violation<Check extends string> = {
|
|
36
|
+
check: Check;
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
export type CheckError<Check extends string> = Error & {
|
|
40
|
+
check: Check;
|
|
41
|
+
};
|
|
42
|
+
export type TransformPluginBase<Check extends string> = {
|
|
43
|
+
checks: readonly Check[];
|
|
44
44
|
name: string;
|
|
45
45
|
/**
|
|
46
46
|
* Ascending. Ties break by registration order.
|
|
@@ -59,7 +59,7 @@ export type TransformPluginBase = {
|
|
|
59
59
|
* `parserPlugins` are Babel parser plugins the file needs; they are unioned across selected plugins
|
|
60
60
|
* and applied before any of them run.
|
|
61
61
|
*/
|
|
62
|
-
export type JsxTransformPlugin = TransformPluginBase & {
|
|
62
|
+
export type JsxTransformPlugin<Check extends string> = TransformPluginBase<Check> & {
|
|
63
63
|
transform: (program: any, context: TransformContext) => void;
|
|
64
64
|
parserPlugins?: readonly string[];
|
|
65
65
|
};
|
|
@@ -68,7 +68,7 @@ export type JsxTransformPlugin = TransformPluginBase & {
|
|
|
68
68
|
* plugin's `validate` or `transform` runs. It returns a promise only when it has something to load, so a stylesheet
|
|
69
69
|
* that needs nothing loaded stays synchronous for PostCSS.
|
|
70
70
|
*/
|
|
71
|
-
export type CssTransformPlugin = TransformPluginBase & {
|
|
71
|
+
export type CssTransformPlugin<Check extends string> = TransformPluginBase<Check> & {
|
|
72
72
|
prepare?: (root: import("postcss").Root, context: CssTransformContext) => Promise<void | {
|
|
73
73
|
fetched: number;
|
|
74
74
|
failed: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-transforms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "Babel and PostCSS checks and transforms for Wix origami page files.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test": "node --test test/*.spec.js",
|
|
14
14
|
"test:browser": "node test/spx.browser.mjs && node test/semanticContainer.browser.mjs",
|
|
15
15
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
16
|
-
"typecheck": "tsc --noEmit"
|
|
16
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.checks.json"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@babel/core": "^7.26.0",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
},
|
|
72
72
|
"type": "module",
|
|
73
73
|
"types": "./dist/index.d.ts",
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "26f18af79b8f5dd98a7aba58e68be7f9dd09c52bdd522e0eb33180c4"
|
|
75
75
|
}
|
|
@@ -6,8 +6,12 @@ import { isLayoutSource } from './layout/parser.js'
|
|
|
6
6
|
// One class or id: DM writes `.compId`, and layout.css still accepts `#compId` (see layout/parser.js).
|
|
7
7
|
const SINGLE_CLASS_OR_ID = /^[.#][\w-]+$/
|
|
8
8
|
|
|
9
|
-
/** @type {
|
|
9
|
+
const checks = /** @type {const} */ ([])
|
|
10
|
+
/** @typedef {typeof checks[number]} Check */
|
|
11
|
+
|
|
12
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
10
13
|
export const componentPositionPlugin = {
|
|
14
|
+
checks,
|
|
11
15
|
name: 'component-position',
|
|
12
16
|
order: 14,
|
|
13
17
|
transform(root, context) {
|
|
@@ -222,8 +222,12 @@ export const removeCustomEnums = root => {
|
|
|
222
222
|
for (const rule of emptied) if (rule?.type === 'rule' && rule.nodes.length === 0) rule.remove()
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
/** @type {
|
|
225
|
+
export const checks = /** @type {const} */ (['duplicate_enum', 'non_literal_enum_value', 'unknown_enum_value', 'enum_parent_selector_list'])
|
|
226
|
+
/** @typedef {typeof checks[number]} Check */
|
|
227
|
+
|
|
228
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
226
229
|
export const customEnumsPlugin = {
|
|
230
|
+
checks,
|
|
227
231
|
name: 'custom-enums',
|
|
228
232
|
// After mixins copy their bodies in, before presets and layout move declarations out of the instance's rule.
|
|
229
233
|
order: 11,
|
|
@@ -311,13 +315,13 @@ export const customEnumsPlugin = {
|
|
|
311
315
|
const matching = enums.filter(item => item.key === declaration.prop && item.inline === nearest)
|
|
312
316
|
if (matching.length === 0) return null
|
|
313
317
|
const key = declaration.prop.slice(2)
|
|
314
|
-
if (matching.length > 1) return `the element has custom enum ${key} more than once, so it can't say which.`
|
|
318
|
+
if (matching.length > 1) return /** @satisfies {[Check, string]} */ (['duplicate_enum', `the element has custom enum ${key} more than once, so it can't say which.`])
|
|
315
319
|
const [found] = matching
|
|
316
320
|
const value = declaration.value.trim()
|
|
317
321
|
const allowed = Object.keys(found.options).join(', ')
|
|
318
|
-
if (/\bvar\(/.test(value)) return `a custom enum's value must be literal, one of ${allowed}.`
|
|
322
|
+
if (/\bvar\(/.test(value)) return /** @satisfies {[Check, string]} */ (['non_literal_enum_value', `a custom enum's value must be literal, one of ${allowed}.`])
|
|
319
323
|
const options = found.options[value]
|
|
320
|
-
if (options === undefined) return `"${value}" is not an option of ${key}; use one of ${allowed}.`
|
|
324
|
+
if (options === undefined) return /** @satisfies {[Check, string]} */ (['unknown_enum_value', `"${value}" is not an option of ${key}; use one of ${allowed}.`])
|
|
321
325
|
return options
|
|
322
326
|
}
|
|
323
327
|
|
|
@@ -337,7 +341,8 @@ export const customEnumsPlugin = {
|
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
// Every declaration is checked before any is rewritten: a failed run leaves the stylesheet as it was.
|
|
340
|
-
|
|
344
|
+
/** @type {Map<string, import('../transform/types.js').Violation<Check>>} */
|
|
345
|
+
const violations = new Map()
|
|
341
346
|
/** @type {any} The first violation's source, whose position the bundler prints. */
|
|
342
347
|
let first
|
|
343
348
|
// The rules with an enum that resolves, the only ones a selector list is split in.
|
|
@@ -348,17 +353,18 @@ export const customEnumsPlugin = {
|
|
|
348
353
|
const results = resolve(rule, own).map(selector => lookup(declaration, selector))
|
|
349
354
|
if (results.some(result => result !== null)) resolving.add(rule)
|
|
350
355
|
if (results.length > 1 && results.some(result => result !== null)) {
|
|
351
|
-
return ['set a custom enum on one instance rule, not under a parent selector list.']
|
|
356
|
+
return [/** @satisfies {[Check, string]} */ (['enum_parent_selector_list', 'set a custom enum on one instance rule, not under a parent selector list.'])]
|
|
352
357
|
}
|
|
353
|
-
return results.filter(result =>
|
|
358
|
+
return results.filter(result => Array.isArray(result))
|
|
354
359
|
})
|
|
355
|
-
for (const problem of problems) {
|
|
360
|
+
for (const [check, problem] of problems) {
|
|
356
361
|
first ??= declaration.source
|
|
357
|
-
|
|
362
|
+
const message = `custom-enums: ${declaration.prop}: ${declaration.value}${where(declaration)}: ${problem}`
|
|
363
|
+
violations.set(message, { check, message })
|
|
358
364
|
}
|
|
359
365
|
}
|
|
360
366
|
if (violations.size) {
|
|
361
|
-
throw new LayoutError([...violations], {
|
|
367
|
+
throw new LayoutError(customEnumsPlugin, [...violations.values()], {
|
|
362
368
|
file: first?.input.file,
|
|
363
369
|
line: first?.start?.line,
|
|
364
370
|
column: first?.start?.column,
|
|
@@ -380,7 +386,7 @@ export const customEnumsPlugin = {
|
|
|
380
386
|
const rule = ruleOf(declaration)
|
|
381
387
|
if (rule === undefined) continue
|
|
382
388
|
const [selector] = resolve(rule, rule.selector)
|
|
383
|
-
const options = /** @type {Exclude<ReturnType<typeof lookup>, string>} */ (lookup(declaration, selector))
|
|
389
|
+
const options = /** @type {Exclude<ReturnType<typeof lookup>, string[]>} */ (lookup(declaration, selector))
|
|
384
390
|
if (options !== null) declaration.replaceWith(expansion(declaration, options))
|
|
385
391
|
}
|
|
386
392
|
},
|
|
@@ -27,8 +27,15 @@ const usesCustomMedia = root => {
|
|
|
27
27
|
return used
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/** @type {
|
|
30
|
+
export const checks = /** @type {const} */ (['unresolved_custom_media'])
|
|
31
|
+
/** @typedef {typeof checks[number]} Check */
|
|
32
|
+
|
|
33
|
+
/** @template {Error} E @param {Check} check @param {E} error */
|
|
34
|
+
export const fail = (check, error) => Object.assign(error, { check })
|
|
35
|
+
|
|
36
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
31
37
|
export const customMediaPlugin = {
|
|
38
|
+
checks,
|
|
32
39
|
name: 'custom-media',
|
|
33
40
|
order: 0,
|
|
34
41
|
transform(root, context) {
|
|
@@ -43,9 +50,9 @@ export const customMediaPlugin = {
|
|
|
43
50
|
// disables the whole block.
|
|
44
51
|
root.walkAtRules('media', atRule => {
|
|
45
52
|
if (CUSTOM_MEDIA_QUERY.test(atRule.params)) {
|
|
46
|
-
throw new Error(
|
|
53
|
+
throw fail('unresolved_custom_media', new Error(
|
|
47
54
|
`@media ${atRule.params} has no usable @custom-media definition: it is missing or cyclic`
|
|
48
|
-
)
|
|
55
|
+
))
|
|
49
56
|
}
|
|
50
57
|
})
|
|
51
58
|
},
|
package/src/cssPlugins/fonts.js
CHANGED
|
@@ -49,8 +49,12 @@ const fontIdsOf = declaration => {
|
|
|
49
49
|
return family.split(',').map(name => name.trim().replace(/^(['"])(.*)\1$/, '$2').toLowerCase())
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** @type {
|
|
52
|
+
const checks = /** @type {const} */ ([])
|
|
53
|
+
/** @typedef {typeof checks[number]} Check */
|
|
54
|
+
|
|
55
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
53
56
|
export const fontsPlugin = {
|
|
57
|
+
checks,
|
|
54
58
|
name: 'fonts',
|
|
55
59
|
// Reads the declarations as authored, before spx rewrites the sizes the family follows.
|
|
56
60
|
order: 5,
|
package/src/cssPlugins/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import { spxPlugin } from './spx/index.js'
|
|
|
29
29
|
import { stackChildrenPlugin } from './stackChildren.js'
|
|
30
30
|
import { wixVectorUrlsPlugin } from './wixVectorUrls.js'
|
|
31
31
|
|
|
32
|
-
/** @type {readonly import('../transform/types.js').CssTransformPlugin[]} */
|
|
32
|
+
/** @type {readonly import('../transform/types.js').CssTransformPlugin<string>[]} */
|
|
33
33
|
export const cssPlugins = Object.freeze([
|
|
34
34
|
customMediaPlugin,
|
|
35
35
|
fontsPlugin,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { compileContainers } from './compiler.js'
|
|
2
2
|
import { parseTargets } from './parser.js'
|
|
3
3
|
|
|
4
|
-
/** @type {
|
|
4
|
+
export const checks = /** @type {const} */ (['unknown_target', 'target_requires_component_id', 'invalid_target_envelope', 'target_not_site_layout', 'duplicate_target', 'target_requires_declarations', 'duplicate_target_property', 'property_wrong_target', 'content_position_not_relative', 'unsupported_overflow', 'unsupported_content_alignment', 'inconsistent_container_display', 'duplicate_root_property', 'property_requires_target'])
|
|
5
|
+
/** @typedef {typeof checks[number]} Check */
|
|
6
|
+
|
|
7
|
+
/** @template {Error} E @param {Check} check @param {E} error */
|
|
8
|
+
export const fail = (check, error) => Object.assign(error, { check })
|
|
9
|
+
|
|
10
|
+
/** @type {import('../../transform/types.js').CssTransformPlugin<Check>} */
|
|
5
11
|
export const layoutPlugin = {
|
|
12
|
+
checks,
|
|
6
13
|
name: 'layout',
|
|
7
14
|
order: 15,
|
|
8
15
|
validate: (root, context) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fail } from './index.js'
|
|
1
2
|
import { basename } from 'node:path'
|
|
2
3
|
import { isValidContainerId } from '@wix/origami-system-components/layout-contract'
|
|
3
4
|
import { relativeToSite } from '../../transform/relativeToSite.js'
|
|
@@ -20,7 +21,7 @@ export const parseTargets = (root, context) => {
|
|
|
20
21
|
const owners = new Map()
|
|
21
22
|
root.walkAtRules('origami-target', target => {
|
|
22
23
|
const name = target.params.trim()
|
|
23
|
-
if (!Object.hasOwn(targets, name)) throw target.error(`Unknown @origami-target ${name}`)
|
|
24
|
+
if (!Object.hasOwn(targets, name)) throw fail('unknown_target', target.error(`Unknown @origami-target ${name}`))
|
|
24
25
|
const owner = target.parent
|
|
25
26
|
// DM writes component rules as .compId; #compId is still accepted.
|
|
26
27
|
if (
|
|
@@ -28,34 +29,34 @@ export const parseTargets = (root, context) => {
|
|
|
28
29
|
!['.', '#'].includes(owner.selector[0]) ||
|
|
29
30
|
!isValidContainerId(owner.selector.slice(1))
|
|
30
31
|
) {
|
|
31
|
-
throw target.error('@origami-target requires a single component ID rule')
|
|
32
|
+
throw fail('target_requires_component_id', target.error('@origami-target requires a single component ID rule'))
|
|
32
33
|
}
|
|
33
34
|
for (let ancestor = owner.parent; ancestor && ancestor.type !== 'root'; ancestor = ancestor.parent) {
|
|
34
35
|
if (
|
|
35
36
|
ancestor.type !== 'atrule' ||
|
|
36
37
|
!['scope', 'media', 'layer', 'supports', 'container'].includes(ancestor.name)
|
|
37
38
|
) {
|
|
38
|
-
throw target.error('@origami-target is only allowed inside layout rule envelopes')
|
|
39
|
+
throw fail('invalid_target_envelope', target.error('@origami-target is only allowed inside layout rule envelopes'))
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
if (!isLayoutSource(target, context))
|
|
42
|
-
throw target.error('@origami-target must originate in site-owned layout.css')
|
|
43
|
+
throw fail('target_not_site_layout', target.error('@origami-target must originate in site-owned layout.css'))
|
|
43
44
|
const siblings = owners.get(owner) ?? []
|
|
44
|
-
if (siblings.some(sibling => sibling.params.trim() === name)) throw target.error(`Duplicate target ${name}`)
|
|
45
|
+
if (siblings.some(sibling => sibling.params.trim() === name)) throw fail('duplicate_target', target.error(`Duplicate target ${name}`))
|
|
45
46
|
const seen = new Set()
|
|
46
47
|
for (const node of target.nodes ?? []) {
|
|
47
48
|
if (node.type === 'comment') continue
|
|
48
|
-
if (node.type !== 'decl') throw node.error('Target bodies must contain declarations only')
|
|
49
|
-
if (seen.has(node.prop)) throw node.error(`Duplicate target property ${node.prop}`)
|
|
49
|
+
if (node.type !== 'decl') throw fail('target_requires_declarations', node.error('Target bodies must contain declarations only'))
|
|
50
|
+
if (seen.has(node.prop)) throw fail('duplicate_target_property', node.error(`Duplicate target property ${node.prop}`))
|
|
50
51
|
seen.add(node.prop)
|
|
51
|
-
if (!targets[name].has(node.prop)) throw node.error(`${node.prop} does not belong to ${name}`)
|
|
52
|
+
if (!targets[name].has(node.prop)) throw fail('property_wrong_target', node.error(`${node.prop} does not belong to ${name}`))
|
|
52
53
|
if (node.prop === 'position' && node.value !== 'relative')
|
|
53
|
-
throw node.error('Content position must be relative')
|
|
54
|
+
throw fail('content_position_not_relative', node.error('Content position must be relative'))
|
|
54
55
|
if (name === OVERFLOW && !['auto', 'scroll', 'hidden', 'visible', 'clip'].includes(node.value)) {
|
|
55
|
-
throw node.error(`Unsupported overflow value ${node.value}`)
|
|
56
|
+
throw fail('unsupported_overflow', node.error(`Unsupported overflow value ${node.value}`))
|
|
56
57
|
}
|
|
57
58
|
if (node.prop === '--content-max-width-align' && !['left', 'center', 'right'].includes(node.value)) {
|
|
58
|
-
throw node.error(`Unsupported content alignment ${node.value}`)
|
|
59
|
+
throw fail('unsupported_content_alignment', node.error(`Unsupported content alignment ${node.value}`))
|
|
59
60
|
}
|
|
60
61
|
if (node.prop === 'display') {
|
|
61
62
|
const type = owner.nodes.find(item => item.type === 'decl' && item.prop === '--container-layout-type')
|
|
@@ -64,7 +65,7 @@ export const parseTargets = (root, context) => {
|
|
|
64
65
|
(type?.type === 'decl' &&
|
|
65
66
|
(!Object.hasOwn(displays, type.value) || displays[type.value] !== node.value))
|
|
66
67
|
) {
|
|
67
|
-
throw node.error('Container type and content display are inconsistent')
|
|
68
|
+
throw fail('inconsistent_container_display', node.error('Container type and content display are inconsistent'))
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -75,10 +76,10 @@ export const parseTargets = (root, context) => {
|
|
|
75
76
|
const seen = new Set()
|
|
76
77
|
for (const node of owner.nodes) {
|
|
77
78
|
if (node.type !== 'decl') continue
|
|
78
|
-
if (seen.has(node.prop)) throw node.error(`Duplicate root property ${node.prop}`)
|
|
79
|
+
if (seen.has(node.prop)) throw fail('duplicate_root_property', node.error(`Duplicate root property ${node.prop}`))
|
|
79
80
|
seen.add(node.prop)
|
|
80
81
|
if ((targets[CONTENT].has(node.prop) && node.prop !== 'position') || targets[OVERFLOW].has(node.prop)) {
|
|
81
|
-
throw node.error(`${node.prop} belongs inside @origami-target`)
|
|
82
|
+
throw fail('property_requires_target', node.error(`${node.prop} belongs inside @origami-target`))
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
}
|
package/src/cssPlugins/mixins.js
CHANGED
|
@@ -7,8 +7,15 @@ const MAX_CLONED_BYTES = 10 * 1024 * 1024
|
|
|
7
7
|
/** @param {import('postcss').AtRule} atRule */
|
|
8
8
|
const mixinName = atRule => atRule.params.trim().replace(/\(\)$/, '')
|
|
9
9
|
|
|
10
|
-
/** @type {
|
|
10
|
+
export const checks = /** @type {const} */ (['missing_mixin', 'cyclic_apply', 'expansion_depth_exceeded', 'cloned_node_budget_exceeded', 'cloned_css_budget_exceeded'])
|
|
11
|
+
/** @typedef {typeof checks[number]} Check */
|
|
12
|
+
|
|
13
|
+
/** @template {Error} E @param {Check} check @param {E} error */
|
|
14
|
+
export const fail = (check, error) => Object.assign(error, { check })
|
|
15
|
+
|
|
16
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
11
17
|
export const mixinsPlugin = {
|
|
18
|
+
checks,
|
|
12
19
|
name: 'mixins',
|
|
13
20
|
order: 10,
|
|
14
21
|
transform(root) {
|
|
@@ -30,16 +37,16 @@ export const mixinsPlugin = {
|
|
|
30
37
|
container.walkAtRules('apply', atRule => {
|
|
31
38
|
const name = mixinName(atRule)
|
|
32
39
|
const body = bodies.get(name)
|
|
33
|
-
if (!body) throw new Error(`@apply ${atRule.params} has no @mixin definition`)
|
|
34
|
-
if (stack.includes(name)) throw new Error(`Cyclic @apply: ${[...stack, name].join(' -> ')}`)
|
|
40
|
+
if (!body) throw fail('missing_mixin', new Error(`@apply ${atRule.params} has no @mixin definition`))
|
|
41
|
+
if (stack.includes(name)) throw fail('cyclic_apply', new Error(`Cyclic @apply: ${[...stack, name].join(' -> ')}`))
|
|
35
42
|
if (stack.length >= MAX_EXPANSION_DEPTH) {
|
|
36
|
-
throw new Error(`@apply ${name} exceeds maximum expansion depth (${MAX_EXPANSION_DEPTH})`)
|
|
43
|
+
throw fail('expansion_depth_exceeded', new Error(`@apply ${name} exceeds maximum expansion depth (${MAX_EXPANSION_DEPTH})`))
|
|
37
44
|
}
|
|
38
45
|
const pending = [...body]
|
|
39
46
|
while (pending.length) {
|
|
40
47
|
const node = pending.pop()
|
|
41
48
|
if (++clonedNodes > MAX_CLONED_NODES) {
|
|
42
|
-
throw new Error(`@apply ${name} exceeds stylesheet cloned-node budget (${MAX_CLONED_NODES})`)
|
|
49
|
+
throw fail('cloned_node_budget_exceeded', new Error(`@apply ${name} exceeds stylesheet cloned-node budget (${MAX_CLONED_NODES})`))
|
|
43
50
|
}
|
|
44
51
|
if (node && 'nodes' in node && node.nodes) {
|
|
45
52
|
for (const child of node.nodes) pending.push(child)
|
|
@@ -48,7 +55,7 @@ export const mixinsPlugin = {
|
|
|
48
55
|
for (const node of body) {
|
|
49
56
|
clonedBytes += Buffer.byteLength((node.raws.before ?? '') + node.toString(), 'utf8')
|
|
50
57
|
if (clonedBytes > MAX_CLONED_BYTES) {
|
|
51
|
-
throw new Error(`@apply ${name} exceeds stylesheet cloned-CSS byte budget (${MAX_CLONED_BYTES})`)
|
|
58
|
+
throw fail('cloned_css_budget_exceeded', new Error(`@apply ${name} exceeds stylesheet cloned-CSS byte budget (${MAX_CLONED_BYTES})`))
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
// PostCSS does not revisit nodes inserted in place of the current at-rule.
|
|
@@ -23,8 +23,12 @@ const target = (selector, key) => {
|
|
|
23
23
|
return selector === first ? `.${id}${WRAPPER}` : null
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** @type {
|
|
26
|
+
const checks = /** @type {const} */ ([])
|
|
27
|
+
/** @typedef {typeof checks[number]} Check */
|
|
28
|
+
|
|
29
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
27
30
|
export const presetsPlugin = {
|
|
31
|
+
checks,
|
|
28
32
|
name: 'presets',
|
|
29
33
|
order: 13,
|
|
30
34
|
transform(root, context) {
|
|
@@ -101,8 +101,12 @@ const layerDeclarations = (compId, vars) => {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/** @type {
|
|
104
|
+
const checks = /** @type {const} */ ([])
|
|
105
|
+
/** @typedef {typeof checks[number]} Check */
|
|
106
|
+
|
|
107
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
105
108
|
export const shapeDividersPlugin = {
|
|
109
|
+
checks,
|
|
106
110
|
name: 'shape-dividers',
|
|
107
111
|
// Last, with wix-vector-urls: it only adds rules, which no other plugin needs to see, and it reads only `--height` as
|
|
108
112
|
// `var(--height)`, so spx converts that var's `--spx()` where it is declared.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fail } from './index.js'
|
|
1
2
|
const CALL = /--spx\(\s*(-?(?:\d+\.?\d*|\.\d+))\s*\)/g
|
|
2
3
|
|
|
3
4
|
/** Replace literal SPX calls, reporting unsupported arguments at their declaration.
|
|
@@ -8,5 +9,5 @@ const CALL = /--spx\(\s*(-?(?:\d+\.?\d*|\.\d+))\s*\)/g
|
|
|
8
9
|
export const lowerCalls = (declaration, resolve, invalidCall) => {
|
|
9
10
|
if (!declaration.value.includes('--spx(')) return
|
|
10
11
|
declaration.value = declaration.value.replace(CALL, (_, literal) => resolve(Number(literal)))
|
|
11
|
-
if (declaration.value.includes('--spx(')) throw invalidCall()
|
|
12
|
+
if (declaration.value.includes('--spx(')) throw fail('non_literal_spx_argument', invalidCall())
|
|
12
13
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fail } from './index.js'
|
|
1
2
|
import { lowerCalls } from './calls.js'
|
|
2
3
|
|
|
3
4
|
// Keep arithmetic at the call site: an inherited derived custom property would already
|
|
@@ -16,7 +17,7 @@ const UNCAPPED_UNIT = unitCappedAt('99999px')
|
|
|
16
17
|
export const compileCascadeDeclaration = declaration => {
|
|
17
18
|
if (declaration.prop === '--spx-resolver-type') {
|
|
18
19
|
const resolver = declaration.value.trim()
|
|
19
|
-
if (!['scale', 'fixed'].includes(resolver)) throw declaration.error('Unsupported SPX resolver')
|
|
20
|
+
if (!['scale', 'fixed'].includes(resolver)) throw fail('unsupported_spx_resolver', declaration.error('Unsupported SPX resolver'))
|
|
20
21
|
const value = resolver === 'scale' ? '1' : '0'
|
|
21
22
|
const previous = declaration.prev()
|
|
22
23
|
if (
|
|
@@ -32,7 +33,7 @@ export const compileCascadeDeclaration = declaration => {
|
|
|
32
33
|
declaration.prop === '--spx-ref-width' &&
|
|
33
34
|
(!Number.isFinite(Number(declaration.value)) || Number(declaration.value) <= 0)
|
|
34
35
|
) {
|
|
35
|
-
throw declaration.error('SPX reference width must be positive')
|
|
36
|
+
throw fail('invalid_spx_reference_width', declaration.error('SPX reference width must be positive'))
|
|
36
37
|
}
|
|
37
38
|
const unit = declaration.prop === '--content-max-width' ? UNCAPPED_UNIT : UNIT
|
|
38
39
|
lowerCalls(
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { compileCascadeDeclaration } from './cascade.js'
|
|
2
2
|
|
|
3
|
-
/** @type {
|
|
3
|
+
export const checks = /** @type {const} */ (['unsupported_spx_resolver', 'invalid_spx_reference_width', 'non_literal_spx_argument'])
|
|
4
|
+
/** @typedef {typeof checks[number]} Check */
|
|
5
|
+
|
|
6
|
+
/** @template {Error} E @param {Check} check @param {E} error */
|
|
7
|
+
export const fail = (check, error) => Object.assign(error, { check })
|
|
8
|
+
|
|
9
|
+
/** @type {import('../../transform/types.js').CssTransformPlugin<Check>} */
|
|
4
10
|
export const spxPlugin = {
|
|
11
|
+
checks,
|
|
5
12
|
name: 'spx',
|
|
6
13
|
order: 20,
|
|
7
14
|
transform(root) {
|
|
@@ -157,8 +157,12 @@ const where = declaration => {
|
|
|
157
157
|
|
|
158
158
|
const or = items => (items.length === 1 ? items[0] : `${items.slice(0, -1).join(', ')} or ${items.at(-1)}`)
|
|
159
159
|
|
|
160
|
-
/** @type {
|
|
160
|
+
export const checks = /** @type {const} */ (['child_not_in_stack'])
|
|
161
|
+
/** @typedef {typeof checks[number]} Check */
|
|
162
|
+
|
|
163
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
161
164
|
export const stackChildrenPlugin = {
|
|
165
|
+
checks,
|
|
162
166
|
name: 'stack-children',
|
|
163
167
|
// After mixins copy their bodies in, before spx rewrites --spx() calls away.
|
|
164
168
|
order: 12,
|
|
@@ -199,7 +203,7 @@ export const stackChildrenPlugin = {
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
if (violations.size) {
|
|
202
|
-
throw new LayoutError([...violations], {
|
|
206
|
+
throw new LayoutError(stackChildrenPlugin, [...violations].map(message => ({ check: 'child_not_in_stack', message })), {
|
|
203
207
|
file: first?.input.file,
|
|
204
208
|
line: first?.start?.line,
|
|
205
209
|
column: first?.start?.column,
|
|
@@ -17,8 +17,12 @@ const idsIn = root => {
|
|
|
17
17
|
return ids
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** @type {
|
|
20
|
+
const checks = /** @type {const} */ ([])
|
|
21
|
+
/** @typedef {typeof checks[number]} Check */
|
|
22
|
+
|
|
23
|
+
/** @type {import('../transform/types.js').CssTransformPlugin<Check>} */
|
|
21
24
|
export const wixVectorUrlsPlugin = {
|
|
25
|
+
checks,
|
|
22
26
|
name: 'wix-vector-urls',
|
|
23
27
|
// Last, so a url a mixin or preset copies elsewhere is inlined wherever it ends up.
|
|
24
28
|
order: 25,
|
package/src/jsxPlugins/index.js
CHANGED
|
@@ -7,5 +7,5 @@ import { origamiCompatibilityPlugin } from './origamiCompatibility/index.js'
|
|
|
7
7
|
import { layoutValidationPlugin } from './validation/index.js'
|
|
8
8
|
import { pageSlotsPlugin } from './pageSlots.js'
|
|
9
9
|
|
|
10
|
-
/** @type {readonly import('../transform/types.js').JsxTransformPlugin[]} */
|
|
10
|
+
/** @type {readonly import('../transform/types.js').JsxTransformPlugin<string>[]} */
|
|
11
11
|
export const jsxPlugins = Object.freeze([layoutValidationPlugin, mediaValidationPlugin, origamiCompatibilityPlugin, pageSlotsPlugin])
|
|
@@ -13,8 +13,12 @@ const STRUCTURE_FILES = new Set(['structure.tsx', 'structure.jsx'])
|
|
|
13
13
|
// `117a96_1cff23187eeb4050a2a691bcdcad6502~mv2.jpg`: an account prefix, an underscore and a file name.
|
|
14
14
|
const WIX_MEDIA_ID = /^[A-Za-z0-9]+_[A-Za-z0-9_~.-]+\.[A-Za-z0-9]+$/
|
|
15
15
|
|
|
16
|
-
/** @type {
|
|
16
|
+
export const checks = /** @type {const} */ (['media_not_wix'])
|
|
17
|
+
/** @typedef {typeof checks[number]} Check */
|
|
18
|
+
|
|
19
|
+
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
17
20
|
export const mediaValidationPlugin = {
|
|
21
|
+
checks,
|
|
18
22
|
name: 'wix-import-media-validation',
|
|
19
23
|
// Alongside the layout rules: before anything rewrites the tree.
|
|
20
24
|
order: -100,
|
|
@@ -22,6 +26,7 @@ export const mediaValidationPlugin = {
|
|
|
22
26
|
|
|
23
27
|
transform: (program, context) => {
|
|
24
28
|
const file = context.relativePath ?? context.filePath
|
|
29
|
+
/** @type {import('../../transform/types.js').Violation<Check>[]} */
|
|
25
30
|
const violations = []
|
|
26
31
|
let first
|
|
27
32
|
program.traverse({
|
|
@@ -34,9 +39,9 @@ export const mediaValidationPlugin = {
|
|
|
34
39
|
if (uri?.value.type !== 'StringLiteral' || WIX_MEDIA_ID.test(uri.value.value)) return
|
|
35
40
|
const id = attributes.find(attribute => attribute.name.name === 'id')?.value?.value
|
|
36
41
|
const line = uri.loc?.start.line
|
|
37
|
-
violations.push(
|
|
42
|
+
violations.push({ check: 'media_not_wix', message:
|
|
38
43
|
`${file}${line ? `:${line}` : ''} <Image${id ? ` id="${id}"` : ''}> image.uri "${uri.value.value}" is not a Wix media ID. Run \`wix origami upload-media\` to upload it.`
|
|
39
|
-
)
|
|
44
|
+
})
|
|
40
45
|
first ??= { file: context.filePath, line, column: uri.loc?.start.column }
|
|
41
46
|
},
|
|
42
47
|
})
|
|
@@ -44,10 +49,10 @@ export const mediaValidationPlugin = {
|
|
|
44
49
|
if (context.command === 'serve') {
|
|
45
50
|
for (const violation of violations) {
|
|
46
51
|
warn('media_not_wix')
|
|
47
|
-
console.warn(`[origami] ${violation}`)
|
|
52
|
+
console.warn(`[origami] ${violation.message}`)
|
|
48
53
|
}
|
|
49
54
|
return
|
|
50
55
|
}
|
|
51
|
-
throw new LayoutError(violations, first)
|
|
56
|
+
throw new LayoutError(mediaValidationPlugin, violations, first)
|
|
52
57
|
},
|
|
53
58
|
}
|
|
@@ -22,8 +22,12 @@ const RULES_BY_COMPONENT = new Map(
|
|
|
22
22
|
])
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
/** @type {
|
|
25
|
+
const checks = /** @type {const} */ ([])
|
|
26
|
+
/** @typedef {typeof checks[number]} Check */
|
|
27
|
+
|
|
28
|
+
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
26
29
|
export const origamiCompatibilityPlugin = {
|
|
30
|
+
checks,
|
|
27
31
|
name: 'wix-import-origami-compatibility',
|
|
28
32
|
order: 0,
|
|
29
33
|
appliesTo: context => basename(context.filePath) === STRUCTURE_FILE,
|
|
@@ -32,8 +32,13 @@ const slotFor = component => {
|
|
|
32
32
|
return undefined
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const checks = /** @type {const} */ ([])
|
|
36
|
+
/** @typedef {typeof checks[number]} Check */
|
|
37
|
+
|
|
38
|
+
/** Transitional support for DM's literal Page children alongside authored slot props.
|
|
39
|
+
* @type {import('../transform/types.js').JsxTransformPlugin<Check>} */
|
|
36
40
|
export const pageSlotsPlugin = {
|
|
41
|
+
checks,
|
|
37
42
|
name: 'wix-import-page-slots',
|
|
38
43
|
order: 0,
|
|
39
44
|
appliesTo: context => basename(context.filePath) === 'structure.jsx',
|
|
@@ -75,7 +75,7 @@ const CHECKS = {
|
|
|
75
75
|
export const assertKnownConstraints = rules => {
|
|
76
76
|
for (const rule of rules) {
|
|
77
77
|
for (const key of Object.keys(rule)) {
|
|
78
|
-
if (key !== 'id' && key !== 'components' && !CHECKS[key]) {
|
|
78
|
+
if (key !== 'id' && key !== 'components' && key !== 'checks' && !CHECKS[key]) {
|
|
79
79
|
throw new Error(
|
|
80
80
|
`layout rules: unknown constraint "${key}" on ${rule.id}. Known: ${Object.keys(CHECKS).join(', ')}`
|
|
81
81
|
)
|
|
@@ -86,20 +86,27 @@ 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.
|
|
90
|
+
* @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
|
+
*/
|
|
90
93
|
export const checkRule = (rule, { node, componentOf, report }) => {
|
|
91
94
|
const prefix = `${describe(node)} ${expectation(rule)}`
|
|
92
|
-
|
|
95
|
+
/** @param {import('./index.js').Check} check */
|
|
96
|
+
const fail = check => got => report(check, `${prefix}; found ${got}.`)
|
|
93
97
|
|
|
94
98
|
const elements = node.children
|
|
95
99
|
.filter(child => child.type === 'JSXElement')
|
|
96
100
|
.map(element => ({ element, component: componentOf(element) }))
|
|
97
|
-
const context = { node, elements
|
|
101
|
+
const context = { node, elements }
|
|
98
102
|
|
|
99
|
-
if (constrainsChildren(rule)) checkLiteral(context)
|
|
100
|
-
for (const [key, value] of Object.entries(rule)) CHECKS[key]?.(value, context)
|
|
103
|
+
if (constrainsChildren(rule)) checkLiteral({ ...context, fail: fail(rule.checks.literal) })
|
|
104
|
+
for (const [key, value] of Object.entries(rule)) CHECKS[key]?.(value, { ...context, fail: fail(rule.checks[key]) })
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
/** `createElement(Section, …)` would hide a constrained component's children from the checks.
|
|
107
|
+
/** `createElement(Section, …)` would hide a constrained component's children from the checks.
|
|
108
|
+
* @param {import('./rules.js').Rule} rule
|
|
109
|
+
* @param {{ node: any, component: string, report: (check: import('./index.js').Check, message: string) => void }} input
|
|
110
|
+
*/
|
|
104
111
|
export const checkCreateElement = (rule, { node, component, report }) =>
|
|
105
|
-
report(`<${component}>${at(node)} ${expectation(rule)}; found createElement(${component}), write it as a JSX tag.`)
|
|
112
|
+
report(rule.checks.createElement, `<${component}>${at(node)} ${expectation(rule)}; found createElement(${component}), write it as a JSX tag.`)
|
|
@@ -26,8 +26,12 @@ const isCreateElement = callee =>
|
|
|
26
26
|
(callee.type === 'Identifier' && callee.name === 'createElement') ||
|
|
27
27
|
(callee.type === 'MemberExpression' && !callee.computed && callee.property.name === 'createElement')
|
|
28
28
|
|
|
29
|
-
/** @type {
|
|
29
|
+
export const checks = /** @type {const} */ (['section_wrong_child', 'section_too_few_children', 'section_too_many_children', 'section_non_literal_children', 'section_create_element'])
|
|
30
|
+
/** @typedef {typeof checks[number]} Check */
|
|
31
|
+
|
|
32
|
+
/** @type {import('../../transform/types.js').JsxTransformPlugin<Check>} */
|
|
30
33
|
export const layoutValidationPlugin = {
|
|
34
|
+
checks,
|
|
31
35
|
name: 'wix-import-layout-validation',
|
|
32
36
|
// Before any plugin that rewrites the tree, so the rules see it as authored.
|
|
33
37
|
order: -100,
|
|
@@ -40,8 +44,10 @@ export const layoutValidationPlugin = {
|
|
|
40
44
|
return name.type === 'JSXIdentifier' ? name.name : undefined
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
/** @type {import('../../transform/types.js').Violation<Check>[]} */
|
|
43
48
|
const violations = []
|
|
44
|
-
|
|
49
|
+
/** @param {Check} check @param {string} message */
|
|
50
|
+
const report = (check, message) => violations.push({ check, message })
|
|
45
51
|
program.traverse({
|
|
46
52
|
JSXElement(path) {
|
|
47
53
|
const component = componentOf(path.node)
|
|
@@ -60,6 +66,6 @@ export const layoutValidationPlugin = {
|
|
|
60
66
|
},
|
|
61
67
|
})
|
|
62
68
|
|
|
63
|
-
if (violations.length) throw new LayoutError(violations)
|
|
69
|
+
if (violations.length) throw new LayoutError(layoutValidationPlugin, violations)
|
|
64
70
|
},
|
|
65
71
|
}
|
|
@@ -13,9 +13,23 @@
|
|
|
13
13
|
|
|
14
14
|
const STANDARD_SECTIONS = ['Section', 'HeaderSection', 'FooterSection']
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} Rule
|
|
18
|
+
* @property {string} id
|
|
19
|
+
* @property {string[]} components
|
|
20
|
+
* @property {string[]} allowedChildren
|
|
21
|
+
* @property {number} minChildren
|
|
22
|
+
* @property {number} maxChildren
|
|
23
|
+
* @property {Record<'allowedChildren' | 'minChildren' | 'maxChildren' | 'literal' | 'createElement', import('./index.js').Check>} checks
|
|
24
|
+
*/
|
|
25
|
+
/** @satisfies {Rule[]} */
|
|
16
26
|
export const RULES = [
|
|
17
27
|
{
|
|
18
28
|
id: 'S2-SECTION-001',
|
|
29
|
+
checks: {
|
|
30
|
+
allowedChildren: 'section_wrong_child', minChildren: 'section_too_few_children',
|
|
31
|
+
maxChildren: 'section_too_many_children', literal: 'section_non_literal_children', createElement: 'section_create_element',
|
|
32
|
+
},
|
|
19
33
|
components: STANDARD_SECTIONS,
|
|
20
34
|
allowedChildren: ['InternalContainer'],
|
|
21
35
|
minChildren: 1,
|
package/src/transform/bi.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { currentRow } from '@wix/origami-bi'
|
|
2
|
+
import { LayoutError } from './layoutError.js'
|
|
2
3
|
const summaries = new WeakMap()
|
|
3
4
|
|
|
4
5
|
/** Aggregate completed dev invocations; paths are only in this local set, never in rows. */
|
|
@@ -32,3 +33,32 @@ export function warn(key) {
|
|
|
32
33
|
const parent = current?.step_type === 'build_plugin' ? current.parent : current
|
|
33
34
|
parent?.runInContext(() => parent.run.openRow('warning', key, { error_name: key }).end())
|
|
34
35
|
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @template {string} Check
|
|
39
|
+
* @param {import('./types.js').TransformPluginBase<Check>} plugin
|
|
40
|
+
* @param {unknown} cause
|
|
41
|
+
* @returns {Check | undefined}
|
|
42
|
+
*/
|
|
43
|
+
export function declaredCheck(plugin, cause) {
|
|
44
|
+
return cause instanceof Error && 'check' in cause ? plugin.checks.find(check => check === cause.check) : undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Finish a plugin failure, projecting only its declared keys into check children. */
|
|
48
|
+
export function recordPluginFailure(plugin, cause, pageId) {
|
|
49
|
+
const row = currentRow()
|
|
50
|
+
const check = declaredCheck(plugin, cause)
|
|
51
|
+
const violations = cause instanceof LayoutError ? cause.violations :
|
|
52
|
+
check !== undefined ? [{ check }] : []
|
|
53
|
+
const layout = cause instanceof LayoutError || violations.length > 0
|
|
54
|
+
row?.end(false, { error_name: layout ? 'layout_error' : 'plugin_error' }, {
|
|
55
|
+
outcome: layout ? 'layout_error' : 'plugin_error', violations: violations.length,
|
|
56
|
+
})
|
|
57
|
+
const counts = new Map()
|
|
58
|
+
for (const { check } of violations) {
|
|
59
|
+
if (plugin.checks.includes(check)) counts.set(check, (counts.get(check) ?? 0) + 1)
|
|
60
|
+
}
|
|
61
|
+
for (const [check, violations] of counts) row?.run.recordRow('build_plugin', plugin.name, {
|
|
62
|
+
...(pageId ? { item_type: 'page', item_id: pageId } : {}), step_details: { check, violations },
|
|
63
|
+
}, { success: false, duration_ms: 0 }, row)
|
|
64
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// the author gets every plugin's violations in one error, at the first one's position, not one plugin's per build.
|
|
7
7
|
|
|
8
8
|
import { currentRow, namedError } from '@wix/origami-bi'
|
|
9
|
-
import { countPlugin } from '../bi.js'
|
|
9
|
+
import { countPlugin, declaredCheck, recordPluginFailure } from '../bi.js'
|
|
10
10
|
import { pageIdFor } from '../context.js'
|
|
11
11
|
import { LayoutError } from '../layoutError.js'
|
|
12
12
|
|
|
@@ -16,12 +16,12 @@ const reason = cause => (cause instanceof Error ? cause.message : String(cause))
|
|
|
16
16
|
export const pluginError = (plugin, context, cause) =>
|
|
17
17
|
// Tooling such as Vite's error overlay prints only the message, so the reason goes in it as well. A
|
|
18
18
|
// plugin may throw any value, so it is read without assuming an Error.
|
|
19
|
-
namedError('plugin_error', `CSS transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`, {
|
|
19
|
+
namedError(declaredCheck(plugin, cause) !== undefined ? 'layout_error' : 'plugin_error', `CSS transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`, {
|
|
20
20
|
cause,
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
export const runCssPlugins = ({ plugins, root, context }) => {
|
|
24
|
-
/** @type {LayoutError[]} */
|
|
24
|
+
/** @type {LayoutError<string>[]} */
|
|
25
25
|
const layoutErrors = []
|
|
26
26
|
for (const phase of ['validate', 'transform']) {
|
|
27
27
|
for (const plugin of plugins) {
|
|
@@ -38,10 +38,7 @@ export const runCssPlugins = ({ plugins, root, context }) => {
|
|
|
38
38
|
if (context.command === 'serve') row?.discard()
|
|
39
39
|
else row?.end()
|
|
40
40
|
} catch (cause) {
|
|
41
|
-
|
|
42
|
-
row?.end(false, { error_name: layout ? 'layout_error' : 'plugin_error' }, {
|
|
43
|
-
outcome: layout ? 'layout_error' : 'plugin_error', violations: layout ? cause.violations.length : 0,
|
|
44
|
-
})
|
|
41
|
+
if (row) row.runInContext(() => recordPluginFailure(plugin, cause, pageId))
|
|
45
42
|
if (cause instanceof LayoutError) {
|
|
46
43
|
layoutErrors.push(cause)
|
|
47
44
|
continue
|
|
@@ -58,6 +55,7 @@ export const runCssPlugins = ({ plugins, root, context }) => {
|
|
|
58
55
|
if (layoutErrors.length === 1) throw layoutErrors[0]
|
|
59
56
|
if (layoutErrors.length > 1) {
|
|
60
57
|
throw new LayoutError(
|
|
58
|
+
{ checks: layoutErrors.flatMap(error => error.plugin.checks) },
|
|
61
59
|
layoutErrors.flatMap(error => error.violations),
|
|
62
60
|
layoutErrors[0].location
|
|
63
61
|
)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// plugin and the file that caused it. A LayoutError is the author's, so it passes through.
|
|
4
4
|
|
|
5
5
|
import { currentRow, namedError } from '@wix/origami-bi'
|
|
6
|
-
import { countPlugin } from '../bi.js'
|
|
6
|
+
import { countPlugin, declaredCheck, recordPluginFailure } from '../bi.js'
|
|
7
7
|
import { pageIdFor } from '../context.js'
|
|
8
8
|
import { LayoutError } from '../layoutError.js'
|
|
9
9
|
|
|
@@ -23,14 +23,11 @@ export const runJsxPlugins = ({ plugins, program, context }) => {
|
|
|
23
23
|
if (context.command === 'serve') row?.discard()
|
|
24
24
|
else row?.end()
|
|
25
25
|
} catch (cause) {
|
|
26
|
-
|
|
27
|
-
row?.end(false, { error_name: layout ? 'layout_error' : 'plugin_error' }, {
|
|
28
|
-
outcome: layout ? 'layout_error' : 'plugin_error', violations: layout ? cause.violations.length : 0,
|
|
29
|
-
})
|
|
26
|
+
if (row) row.runInContext(() => recordPluginFailure(plugin, cause, pageId))
|
|
30
27
|
if (cause instanceof LayoutError) throw cause
|
|
31
28
|
// Tooling such as Vite's error overlay prints only the message, so the reason goes in it as well. A
|
|
32
29
|
// plugin may throw any value, so it is read without assuming an Error.
|
|
33
|
-
throw namedError('plugin_error',
|
|
30
|
+
throw namedError(declaredCheck(plugin, cause) !== undefined ? 'layout_error' : 'plugin_error',
|
|
34
31
|
`JSX transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`,
|
|
35
32
|
{ cause }
|
|
36
33
|
)
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
// without naming the plugin, and it carries no stack: the bundler already names the file, and the
|
|
3
3
|
// messages say where in it.
|
|
4
4
|
|
|
5
|
+
/** @template {string} Check */
|
|
5
6
|
export class LayoutError extends Error {
|
|
6
7
|
/**
|
|
7
|
-
* @param {
|
|
8
|
+
* @param {Pick<import('./types.js').TransformPluginBase<Check>, 'checks'>} plugin
|
|
9
|
+
* @param {readonly import('./types.js').Violation<NoInfer<Check>>[]} violations
|
|
8
10
|
* @param {{ file?: string, line?: number, column?: number }} [location] The first violation's, which the
|
|
9
11
|
* bundler prints as the error's position (Vite's CSS stage reads `file`, `line` and `column`).
|
|
10
12
|
*/
|
|
11
|
-
constructor(violations, location = {}) {
|
|
12
|
-
|
|
13
|
+
constructor(plugin, violations, location = {}) {
|
|
14
|
+
const messages = violations.map(violation => violation.message)
|
|
15
|
+
super(messages.length === 1 ? messages[0] : `\n ${messages.join('\n ')}`)
|
|
13
16
|
this.name = 'LayoutError'
|
|
14
17
|
this.error_name = 'layout_error'
|
|
15
18
|
this.stack = ''
|
|
19
|
+
this.plugin = plugin
|
|
16
20
|
this.violations = violations
|
|
17
21
|
this.location = location
|
|
18
22
|
Object.assign(this, location)
|
|
@@ -30,8 +30,8 @@ const createState = ({ layout, registries, command }) => {
|
|
|
30
30
|
/**
|
|
31
31
|
* @param {{
|
|
32
32
|
* layout: import('./types.js').SiteLayout,
|
|
33
|
-
* jsxPlugins?: readonly import('./types.js').JsxTransformPlugin[],
|
|
34
|
-
* cssPlugins?: readonly import('./types.js').CssTransformPlugin[],
|
|
33
|
+
* jsxPlugins?: readonly import('./types.js').JsxTransformPlugin<string>[],
|
|
34
|
+
* cssPlugins?: readonly import('./types.js').CssTransformPlugin<string>[],
|
|
35
35
|
* }} options
|
|
36
36
|
*/
|
|
37
37
|
export const createTransformPipeline = ({ layout, jsxPlugins = defaultJsxPlugins, cssPlugins = defaultCssPlugins }) => {
|
package/src/transform/types.js
CHANGED
|
@@ -18,15 +18,32 @@
|
|
|
18
18
|
* @property {SiteLayout} layout
|
|
19
19
|
* @property {'serve' | 'build'} command
|
|
20
20
|
* @property {boolean} ssr Always false for the CSS stage, which PostCSS drives without an SSR signal.
|
|
21
|
-
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @template {string} Check
|
|
25
|
+
* @typedef {{ check: Check, message: string }} Violation
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @template {string} Check
|
|
30
|
+
* @typedef {Error & { check: Check }} CheckError
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @template {string} Check
|
|
22
35
|
* @typedef {object} TransformPluginBase
|
|
36
|
+
* @property {readonly Check[]} checks
|
|
23
37
|
* @property {string} name
|
|
24
38
|
* @property {number} order Ascending. Ties break by registration order.
|
|
25
39
|
* @property {(context: TransformContext) => boolean} [appliesTo] Whether the plugin runs for this
|
|
26
40
|
* file. Must depend only on the file's identity, not on anything that can change between transforms
|
|
27
41
|
* of the same file: the pipeline memoizes the selection per file path.
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @template {string} Check
|
|
46
|
+
* @typedef {TransformPluginBase<Check> & {
|
|
30
47
|
* transform: (program: any, context: TransformContext) => void,
|
|
31
48
|
* parserPlugins?: readonly string[]
|
|
32
49
|
* }} JsxTransformPlugin
|
|
@@ -34,8 +51,11 @@
|
|
|
34
51
|
* nothing walks it for you, so a plugin that can rule itself out cheaply should just return.
|
|
35
52
|
* `parserPlugins` are Babel parser plugins the file needs; they are unioned across selected plugins
|
|
36
53
|
* and applied before any of them run.
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @template {string} Check
|
|
58
|
+
* @typedef {TransformPluginBase<Check> & {
|
|
39
59
|
* prepare?: (root: import('postcss').Root, context: CssTransformContext) => Promise<void | { fetched: number, failed: number }> | void,
|
|
40
60
|
* validate?: (root: import('postcss').Root, context: CssTransformContext) => void,
|
|
41
61
|
* transform: (root: import('postcss').Root, context: CssTransformContext) => void
|
|
@@ -43,7 +63,9 @@
|
|
|
43
63
|
* `prepare` loads what the plugin needs that can't be read synchronously, such as a file from a CDN, before any
|
|
44
64
|
* plugin's `validate` or `transform` runs. It returns a promise only when it has something to load, so a stylesheet
|
|
45
65
|
* that needs nothing loaded stays synchronous for PostCSS.
|
|
46
|
-
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
47
69
|
* @typedef {TransformContext & {
|
|
48
70
|
* addDependency?: (file: string) => void,
|
|
49
71
|
* warn?: (node: import('postcss').Node, text: string, key?: 'malformed_custom_enum' | 'unmappable_preset' | 'svg_not_loaded') => void
|