@wix/origami-lint 1.34.29 → 1.34.30
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/rules/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import cssLayoutProperties from "./site-app/css-layout-properties.js";
|
|
|
7
7
|
import cssSelectors from "./site-app/css-selectors.js";
|
|
8
8
|
import dataValues from "./site-app/data-values.js";
|
|
9
9
|
import globalStyles from "./site-app/global-styles.js";
|
|
10
|
+
import knownTags from "./site-app/known-tags.js";
|
|
10
11
|
import noAbsoluteSection from "./site-app/no-absolute-section.js";
|
|
11
12
|
import pageStructure from "./site-app/page-structure.js";
|
|
12
13
|
import slotAttributes from "./site-app/slot-attributes.js";
|
|
@@ -16,5 +17,5 @@ import themeFontKnown from "./site-app/theme-font-known.js";
|
|
|
16
17
|
import themeRoot from "./site-app/theme-root.js";
|
|
17
18
|
import noFreeCode from "./site-app/no-free-code.js";
|
|
18
19
|
// Execution registries combine already-checked rules; only this boundary erases their individual key unions.
|
|
19
|
-
export const ruleRegistry = [allowedImports, noFreeCode, pageStructure, noAbsoluteSection, themeFontIds, themeFontKnown, slotAttributes, themeRoot, breakpoints, cssFileNesting, globalStyles, applyAndPresets, bindings, dataValues, cssLayoutProperties, cssSelectors, slotPlacement];
|
|
20
|
+
export const ruleRegistry = [allowedImports, noFreeCode, pageStructure, noAbsoluteSection, themeFontIds, themeFontKnown, slotAttributes, themeRoot, breakpoints, cssFileNesting, globalStyles, applyAndPresets, bindings, dataValues, cssLayoutProperties, cssSelectors, slotPlacement, knownTags];
|
|
20
21
|
export { defineRule } from "./site-app/define-rule.js";
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
// refuses a tag from them, parseJsx.ts:471, identity.ts:80; a call is free code, and fn() / ctx() refuse them as a source).
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { CSS_FILES, JSX_FILES } from "../../page-model/index.js";
|
|
6
|
-
import { BUILDERS, RUNTIME_ONLY_BUILDERS, RUNTIME_PACKAGE } from "../../rules-api/index.js";
|
|
6
|
+
import { BUILDERS, INTERACTIONS_PACKAGES, RUNTIME_ONLY_BUILDERS, RUNTIME_PACKAGE } from "../../rules-api/index.js";
|
|
7
7
|
import { GLOBAL_COMPONENTS, GLOBAL_COMPONENTS_FOLDER, GLOBAL_FOLDER, SITE_SRC } from "../../shared/site-files-tree-mapping.js";
|
|
8
8
|
import { defineRule } from "./define-rule.js";
|
|
9
9
|
const CHECKS = ['disallowed_import_source', 'interaction_import', 'missing_global_import_name', 'invalid_global_import_name', 'default_builder_import', 'aliased_builder_import', 'wrong_builder_source'];
|
|
10
|
-
/** The interactions packages, which the import reserves: structure.jsx imports nothing from them. Matched by source, not by name: `Interact` from @site/site-code is the site's own. */
|
|
11
|
-
const INTERACTIONS_PACKAGES = ['interactReact', 'interact', 'motionPresets'];
|
|
12
10
|
const SOURCE_HINT = `Only packages, files in ${path.posix.basename(GLOBAL_FOLDER)}/, and ${CSS_FILES.map((file) => `'./${file}'`).join(' or ')} can be.`;
|
|
13
11
|
export default defineRule({
|
|
14
12
|
name: 'allowed-imports',
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// How a structure file writes its tags, as the converter reads them (tagOf and validTagOrIssue, parseJsx.ts, converter 0.88):
|
|
2
|
+
// - a tag is <Name> or <NS.Name>: no <svg:rect>, no <UI.Forms.Input> (tracker CONV-parseJsx.ts:401);
|
|
3
|
+
// - a lowercase tag is an HTML element, which the import refuses: <div> (CONV-parseJsx.ts:379). page-structure already reports
|
|
4
|
+
// one at the top of the file or directly in a section, so this rule doesn't. <NS.Name> needs `import * as NS` (379);
|
|
5
|
+
// - no component comes from a source that holds none: the runtime (except <Remove>), a menu, `document/…`, @wix/sdk (CONV-parseJsx.ts:351).
|
|
6
|
+
// The DM's own `document` holds the classic components (<ClassicSection>). allowed-imports reports @wix/interact and
|
|
7
|
+
// @wix/motion-presets on the import line;
|
|
8
|
+
// - a tag's name is capitalized (CONV-parseJsx.ts:359). It reads the local name: the export imports a context provider as
|
|
9
|
+
// `import { cartContext as CartContext }`, and only the site's providers tell it apart from `import { image as Image }`.
|
|
10
|
+
// A capitalized tag with no import, <NS.Name> with no NS, and a namespace written as <NS> are TypeScript's (#409): it reports a name not in scope.
|
|
11
|
+
import { INTERACTIONS_PACKAGES, SECTIONS } from "../../rules-api/index.js";
|
|
12
|
+
import { defineRule } from "./define-rule.js";
|
|
13
|
+
const CHECKS = ['unsupported_tag_name', 'html_tag', 'named_import_as_namespace', 'tag_from_reserved_source', 'lowercase_tag'];
|
|
14
|
+
/** The converter's TAG_PATTERN (identity.ts). */
|
|
15
|
+
const TAG_PATTERN = /^[A-Z][A-Za-z0-9_$]*$/;
|
|
16
|
+
/** The sources the converter reserves (isReservedRegistry, identity.ts) that allowed-imports doesn't already report. */
|
|
17
|
+
const RESERVED_SOURCES = ['runtime', 'menu', 'document', 'sdk'];
|
|
18
|
+
/** The one tag the runtime has: the <Remove> wrapper. */
|
|
19
|
+
const RUNTIME_TAG = 'Remove';
|
|
20
|
+
export default defineRule({
|
|
21
|
+
name: 'known-tags',
|
|
22
|
+
checks: CHECKS,
|
|
23
|
+
editors: ['studio2'],
|
|
24
|
+
callbacks: {
|
|
25
|
+
JSXElement: (input) => {
|
|
26
|
+
if (!input.rulesApi.getComponent(input.node))
|
|
27
|
+
return;
|
|
28
|
+
if (!checkTagNameShape(input))
|
|
29
|
+
return;
|
|
30
|
+
if (!checkTagNotHtml(input))
|
|
31
|
+
return;
|
|
32
|
+
if (!checkTagNamespaceImport(input))
|
|
33
|
+
return;
|
|
34
|
+
if (!checkTagSource(input))
|
|
35
|
+
return;
|
|
36
|
+
checkTagCapitalized(input);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
// ── the steps: each reports its problem; one that returns false ends the steps for the tag, as the converter stops at its first ──
|
|
41
|
+
/** Reports <svg:rect> and <UI.Forms.Input> (CONV-parseJsx.ts:401). */
|
|
42
|
+
function checkTagNameShape({ node, rulesApi, report }) {
|
|
43
|
+
const { name } = node.openingElement;
|
|
44
|
+
if (name.type === 'JSXIdentifier' || (name.type === 'JSXMemberExpression' && name.object.type === 'JSXIdentifier'))
|
|
45
|
+
return true;
|
|
46
|
+
report({ check: 'unsupported_tag_name', loc: name.loc, message: `<${rulesApi.getTagName(node)}> is not a supported tag name: write <Name> or <NS.Name> (one namespace import, one dot).` });
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/** Reports a lowercase tag with no import: an HTML element (CONV-parseJsx.ts:379), unless page-structure reports it already. */
|
|
50
|
+
function checkTagNotHtml({ node, rulesApi, report }) {
|
|
51
|
+
const { name } = node.openingElement;
|
|
52
|
+
if (name.type !== 'JSXIdentifier' || !/^[a-z]/.test(name.name) || rulesApi.getImport(node))
|
|
53
|
+
return true;
|
|
54
|
+
if (!isReportedByPageStructure(node, rulesApi)) {
|
|
55
|
+
report({ check: 'html_tag', loc: name.loc, message: `<${name.name}> is an HTML tag, which the import refuses: use an imported component (import { Name } from '@origami-wrappers/<app>/<name>', then <Name>).` });
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/** Reports <NS.Name> whose NS is imported by name, not as `import * as NS` (CONV-parseJsx.ts:379). With no import at all, it's TypeScript's; from an interactions package, allowed-imports'. */
|
|
60
|
+
function checkTagNamespaceImport({ node, rulesApi, report }) {
|
|
61
|
+
const { name } = node.openingElement;
|
|
62
|
+
const found = rulesApi.getImport(node);
|
|
63
|
+
if (name.type !== 'JSXMemberExpression' || name.object.type !== 'JSXIdentifier' || !found || found.imported === '*' || (found.kind && INTERACTIONS_PACKAGES.includes(found.kind)))
|
|
64
|
+
return true;
|
|
65
|
+
const namespace = name.object.name;
|
|
66
|
+
report({ check: 'named_import_as_namespace', loc: name.loc, message: `<${namespace}.${name.property.name}> reads \`${namespace}\` as a namespace, but \`${namespace}\` is imported by name: import it as \`import * as ${namespace} from '${found.source}'\`, or import ${name.property.name} by name and write <${name.property.name}>.` });
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
/** Reports a tag imported from a source that holds no components (CONV-parseJsx.ts:351). <Remove> from the runtime is a wrapper. */
|
|
70
|
+
function checkTagSource({ node, rulesApi, report }) {
|
|
71
|
+
const found = rulesApi.getImport(node);
|
|
72
|
+
if (!found?.kind || !RESERVED_SOURCES.includes(found.kind) || (found.kind === 'runtime' && found.imported === RUNTIME_TAG))
|
|
73
|
+
return true;
|
|
74
|
+
const tag = rulesApi.getTagName(node);
|
|
75
|
+
const menuHint = found.kind === 'menu' ? `, and use the menu as a value (items: ${tag}.items)` : '';
|
|
76
|
+
report({ check: 'tag_from_reserved_source', loc: node.openingElement.name.loc, message: `<${tag}> is imported from '${found.source}', which holds no components (menus, runtime builders, document/…, @wix/sdk): import the component from its @origami-wrappers package${menuHint}.` });
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
/** Reports a tag whose name isn't capitalized (CONV-parseJsx.ts:359): its local name, and the name the converter reads (the imported name), both.
|
|
80
|
+
* A namespace as a tag is TypeScript's; a tag from an interactions package, allowed-imports'. */
|
|
81
|
+
function checkTagCapitalized({ node, rulesApi, report }) {
|
|
82
|
+
const { name } = node.openingElement;
|
|
83
|
+
const found = rulesApi.getImport(node);
|
|
84
|
+
if (name.type !== 'JSXIdentifier' || !found || found.imported === '*' || (found.kind && INTERACTIONS_PACKAGES.includes(found.kind)))
|
|
85
|
+
return;
|
|
86
|
+
if (TAG_PATTERN.test(name.name) || TAG_PATTERN.test(found.imported))
|
|
87
|
+
return;
|
|
88
|
+
report({ check: 'lowercase_tag', loc: name.loc, message: `<${name.name}> must be capitalized: import the component under its capitalized export name (import { Name } from '${found.source}') and write <Name>; a lowercase tag is an HTML element.` });
|
|
89
|
+
}
|
|
90
|
+
// ── reading the code ──
|
|
91
|
+
/** page-structure reports an element at the top of the file, and one directly in a section: its closest holder, wrappers skipped, is none or a section. */
|
|
92
|
+
function isReportedByPageStructure(node, rulesApi) {
|
|
93
|
+
for (let holder = node.parent; holder; holder = holder.parent) {
|
|
94
|
+
if (holder.type !== 'JSXElement' || rulesApi.isWrapper(holder))
|
|
95
|
+
continue;
|
|
96
|
+
const type = rulesApi.getType(holder);
|
|
97
|
+
return type !== null && SECTIONS.includes(type) && rulesApi.getChildElements(holder).includes(node);
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
package/dist/rules-api/index.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
export { createRulesApi, getOrCreateRulesApi } from "./rules-api.js";
|
|
3
3
|
export { createCssFileReporter, describeAtRule, reportCssProblem } from "./report.js";
|
|
4
4
|
export { bare, getAttribute, getExpression, getKey, getName, getObject, getProperties, getCallbackKind, getParamReadKind, getProperty, isBound, isOneEditAway, isRecord, isReservedAttribute, isStructure, isReadByImport, ownerOf, } from "../shared/utils.js";
|
|
5
|
-
export { BUILDERS, CATALOG_FONTS, CALLBACK_PROP, DATA_KEY, ELEMENT_PROPS, EVENT_ATTRIBUTE, GLOBAL_SECTIONS, INSTANCE_PROPS, LAYER_ATTRIBUTES, RESERVED_ATTRIBUTES, RESERVED_ENTRY_KEYS, RUNTIME_ONLY_BUILDERS, RUNTIME_PACKAGE, SECTIONS, SHARED_COMP_ID, STRUCTURE, SYSTEM_PACKAGE, TRANSFORM_BUILDERS, TYPED_BUILDERS, WRAPPERS, } from "../shared/constants.js";
|
|
5
|
+
export { BUILDERS, CATALOG_FONTS, CALLBACK_PROP, DATA_KEY, ELEMENT_PROPS, EVENT_ATTRIBUTE, GLOBAL_SECTIONS, INSTANCE_PROPS, INTERACTIONS_PACKAGES, LAYER_ATTRIBUTES, RESERVED_ATTRIBUTES, RESERVED_ENTRY_KEYS, RUNTIME_ONLY_BUILDERS, RUNTIME_PACKAGE, SECTIONS, SHARED_COMP_ID, STRUCTURE, SYSTEM_PACKAGE, TRANSFORM_BUILDERS, TYPED_BUILDERS, WRAPPERS, } from "../shared/constants.js";
|
|
6
6
|
export { BREAKPOINT_AT_RULES, CONTENT_TARGET, getCssChildren, getDashedId, getMixinId, getSegmentClass, getSelectorSegments, isCssIdentifier, isPresetProperty, isSafeCssIdentifier, ORIGAMI_TARGETS, OVERFLOW_TARGET, PREAMBLE_LAYER, readFontShorthand, readSegment } from "../shared/css.js";
|
package/dist/shared/constants.js
CHANGED
|
@@ -8,6 +8,8 @@ export const SECTIONS = ['section', 'header', 'footer'];
|
|
|
8
8
|
export const GLOBAL_SECTIONS = ['globalHeader', 'globalFooter', 'globalSection'];
|
|
9
9
|
/** A tag that wraps one component and isn't one itself: `<Remove when={…}>`, `<Interaction interactKey="…">`, `<PinnedToScreen>` (the import drops it). */
|
|
10
10
|
export const WRAPPERS = ['remove', 'interaction', 'pinnedToScreen'];
|
|
11
|
+
/** The interactions packages, which the import reserves: structure.jsx imports nothing from them. Matched by source, not by name: `Interact` from @site/site-code is the site's own. */
|
|
12
|
+
export const INTERACTIONS_PACKAGES = ['interactReact', 'interact', 'motionPresets'];
|
|
11
13
|
/** The name a structure file exports its tree under: `export const Structure = () => (…)`. */
|
|
12
14
|
export const STRUCTURE = 'Structure';
|
|
13
15
|
/** The props a global component's Structure may take: its instance identity, `({ id, className })` (#300). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-lint",
|
|
3
|
-
"version": "1.34.
|
|
3
|
+
"version": "1.34.30",
|
|
4
4
|
"description": "Offline lint for an exported Origami site: lints the pages that changed since the last passing run",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
]
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
-
"falconPackageHash": "
|
|
55
|
+
"falconPackageHash": "335c078a8c6afc26c556fcd6c71643e777664f3b26b63a3b7b9832a4"
|
|
56
56
|
}
|