@ctrl-ui/react 0.9.12 → 0.9.14
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/README.md +20 -7
- package/dist/blocks/design-canvas.js +16 -10
- package/dist/knob-contracts/field-knobs.d.ts +1 -1
- package/dist/knob-contracts/field-knobs.js +1 -0
- package/dist/knob-contracts/gradient-editor-knobs.d.ts +1 -1
- package/dist/knob-contracts/gradient-editor-knobs.js +1 -4
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +2 -0
- package/dist/lib/phone-input-format.js +1 -1
- package/dist/styles/recipes/card.css +8 -0
- package/dist/styles/recipes/color-picker-values.css +4 -18
- package/dist/styles/recipes/color-picker.css +3 -4
- package/dist/styles/recipes/field-parts.css +8 -1
- package/dist/styles/recipes/gradient-editor.css +3 -47
- package/dist/styles/recipes/markdown.css +5 -1
- package/dist/styles/recipes/popup-lists.css +17 -0
- package/dist/styles/recipes/popup.css +3 -0
- package/dist/styles/theme.css +4 -0
- package/dist/ui/autocomplete.d.ts +3 -0
- package/dist/ui/autocomplete.js +17 -2
- package/dist/ui/chart-cartesian.d.ts +60 -57
- package/dist/ui/chart-cartesian.js +7 -7
- package/dist/ui/chart-colors.d.ts +4 -2
- package/dist/ui/chart-colors.js +6 -2
- package/dist/ui/chart-marks.d.ts +4 -4
- package/dist/ui/chart-marks.js +14 -5
- package/dist/ui/chart-series.d.ts +9 -6
- package/dist/ui/chart-series.js +3 -2
- package/dist/ui/color-picker-swatches.d.ts +4 -3
- package/dist/ui/color-picker-swatches.js +7 -8
- package/dist/ui/command.d.ts +12 -0
- package/dist/ui/command.js +31 -1
- package/dist/ui/field.js +1 -1
- package/dist/ui/gradient-editor.d.ts +3 -2
- package/dist/ui/gradient-editor.js +7 -8
- package/dist/ui/markdown-components.d.ts +27 -0
- package/dist/ui/markdown-components.js +57 -0
- package/dist/ui/markdown-elements.d.ts +6 -25
- package/dist/ui/markdown-elements.js +19 -53
- package/dist/ui/markdown.d.ts +2 -7
- package/dist/ui/markdown.js +3 -20
- package/dist/ui/phone-input.js +2 -2
- package/lint/biome-preset.json +8 -1
- package/lint/class-policy.mjs +50 -1
- package/lint/eslint-plugin.mjs +98 -49
- package/lint/grit-rules.mjs +131 -42
- package/lint/no-component-part-css.grit +16 -0
- package/lint/no-component-style-classes.grit +66 -36
- package/lint/no-component-style-props.grit +51 -0
- package/lint/no-raw-type-classes.grit +1 -1
- package/package.json +5 -1
package/lint/eslint-plugin.mjs
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
componentPartClass,
|
|
3
|
+
componentPartMessage,
|
|
4
|
+
componentStyleClass,
|
|
5
|
+
componentStyleKey,
|
|
6
|
+
componentStyleMessage,
|
|
7
|
+
componentStylePropMessage,
|
|
8
|
+
controlUiImportPattern,
|
|
9
|
+
rawTypeClass,
|
|
10
|
+
rawTypeMessage,
|
|
11
|
+
} from "./class-policy.mjs";
|
|
2
12
|
|
|
3
13
|
const componentStyle = new RegExp(componentStyleClass);
|
|
14
|
+
const componentPart = new RegExp(componentPartClass);
|
|
15
|
+
const componentStyleProperty = new RegExp(`^(?:${componentStyleKey})$`);
|
|
4
16
|
const rawType = new RegExp(rawTypeClass);
|
|
5
17
|
|
|
6
18
|
function stringsIn(node) {
|
|
@@ -20,19 +32,30 @@ function referencedNames(node) {
|
|
|
20
32
|
return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : referencedNames(child)));
|
|
21
33
|
}
|
|
22
34
|
|
|
35
|
+
function propertyKeysIn(node) {
|
|
36
|
+
if (Array.isArray(node)) return node.flatMap(propertyKeysIn);
|
|
37
|
+
if (!node || typeof node !== "object") return [];
|
|
38
|
+
const key = node.type === "Property" && !node.computed ? [node.key.name ?? node.key.value] : [];
|
|
39
|
+
return [...key, ...Object.entries(node).flatMap(([name, child]) => (name === "parent" ? [] : propertyKeysIn(child)))];
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
const classLiteralTypes = new Set(["Literal", "TemplateLiteral", "ObjectExpression"]);
|
|
43
|
+
const styleLiteralTypes = new Set(["ObjectExpression"]);
|
|
24
44
|
|
|
25
|
-
function
|
|
45
|
+
function literalOf(init, types) {
|
|
26
46
|
const value = init?.type === "TSAsExpression" || init?.type === "TSSatisfiesExpression" ? init.expression : init;
|
|
27
|
-
return
|
|
47
|
+
return types.has(value?.type) ? value : undefined;
|
|
28
48
|
}
|
|
29
49
|
|
|
30
|
-
function
|
|
31
|
-
|
|
50
|
+
function insideRenderProp(node) {
|
|
51
|
+
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
52
|
+
if (parent.type === "JSXAttribute" && parent.name.name === "render") return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
function classNameMatches(attribute, pattern) {
|
|
35
|
-
return
|
|
58
|
+
return attribute.name.name === "className" && Boolean(attribute.value) && stringsIn(attribute.value).some((text) => pattern.test(text));
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
function elementRootName(name) {
|
|
@@ -59,50 +82,75 @@ function controlUiLocalNames(program, controlUiImport) {
|
|
|
59
82
|
return names;
|
|
60
83
|
}
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if ([...stringsIn(attribute.value), ...constants].some((text) => componentStyle.test(text))) {
|
|
99
|
-
context.report({ node: attribute, messageId: "componentStyle" });
|
|
85
|
+
function componentAttributeRule({ attribute, description, message, literalTypes, valuesIn, pattern, part }) {
|
|
86
|
+
return {
|
|
87
|
+
meta: {
|
|
88
|
+
type: "problem",
|
|
89
|
+
docs: { description },
|
|
90
|
+
messages: { component: message, ...(part && { part: part.message }) },
|
|
91
|
+
schema: [
|
|
92
|
+
{
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: { importSources: { type: "array", items: { type: "string" } } },
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
create(context) {
|
|
100
|
+
const controlUiImport = new RegExp(`^${controlUiImportPattern(context.options[0]?.importSources)}`);
|
|
101
|
+
let controlUiNames = new Set();
|
|
102
|
+
const isControlUi = (element) => element !== undefined && controlUiNames.has(elementRootName(element.name));
|
|
103
|
+
const constants = new Map();
|
|
104
|
+
const checks = [];
|
|
105
|
+
return {
|
|
106
|
+
Program(program) {
|
|
107
|
+
controlUiNames = controlUiLocalNames(program, controlUiImport);
|
|
108
|
+
},
|
|
109
|
+
VariableDeclarator(declarator) {
|
|
110
|
+
const literal = literalOf(declarator.init, literalTypes);
|
|
111
|
+
if (declarator.parent.kind !== "const" || declarator.id.type !== "Identifier" || !literal) return;
|
|
112
|
+
constants.set(declarator.id.name, [...(constants.get(declarator.id.name) ?? []), ...valuesIn(literal)]);
|
|
113
|
+
},
|
|
114
|
+
JSXAttribute(node) {
|
|
115
|
+
if (node.name.name !== attribute || !node.value) return;
|
|
116
|
+
const element = node.parent;
|
|
117
|
+
if (isControlUi(element) || isControlUi(renderPropOwner(element.parent))) {
|
|
118
|
+
checks.push({ node, messageId: "component", pattern });
|
|
119
|
+
} else if (part && !insideRenderProp(node)) {
|
|
120
|
+
checks.push({ node, messageId: "part", pattern: part.pattern });
|
|
100
121
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
},
|
|
123
|
+
"Program:exit"() {
|
|
124
|
+
for (const check of checks) {
|
|
125
|
+
const referenced = referencedNames(check.node.value).flatMap((name) => constants.get(name) ?? []);
|
|
126
|
+
if ([...valuesIn(check.node.value), ...referenced].some((value) => check.pattern.test(value))) {
|
|
127
|
+
context.report({ node: check.node, messageId: check.messageId });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const noComponentStyleClasses = componentAttributeRule({
|
|
137
|
+
attribute: "className",
|
|
138
|
+
description: "Keep className on Control UI components to layout, and off their parts; the component owns its look.",
|
|
139
|
+
message: componentStyleMessage,
|
|
140
|
+
literalTypes: classLiteralTypes,
|
|
141
|
+
valuesIn: stringsIn,
|
|
142
|
+
pattern: componentStyle,
|
|
143
|
+
part: { message: componentPartMessage, pattern: componentPart },
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const noComponentStyleProps = componentAttributeRule({
|
|
147
|
+
attribute: "style",
|
|
148
|
+
description: "Keep style on Control UI components to --cui-* knobs and layout; the component owns its look.",
|
|
149
|
+
message: componentStylePropMessage,
|
|
150
|
+
literalTypes: styleLiteralTypes,
|
|
151
|
+
valuesIn: propertyKeysIn,
|
|
152
|
+
pattern: componentStyleProperty,
|
|
153
|
+
});
|
|
106
154
|
|
|
107
155
|
const noRawTypeClasses = {
|
|
108
156
|
meta: {
|
|
@@ -124,6 +172,7 @@ const plugin = {
|
|
|
124
172
|
meta: { name: "control-ui" },
|
|
125
173
|
rules: {
|
|
126
174
|
"no-component-style-classes": noComponentStyleClasses,
|
|
175
|
+
"no-component-style-props": noComponentStyleProps,
|
|
127
176
|
"no-raw-type-classes": noRawTypeClasses,
|
|
128
177
|
},
|
|
129
178
|
};
|
package/lint/grit-rules.mjs
CHANGED
|
@@ -1,30 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
componentPartClass,
|
|
3
|
+
componentPartCssMessage,
|
|
4
|
+
componentPartMessage,
|
|
5
|
+
componentPartSelector,
|
|
6
|
+
componentStyleClass,
|
|
7
|
+
componentStyleKey,
|
|
8
|
+
componentStyleMessage,
|
|
9
|
+
componentStylePropMessage,
|
|
10
|
+
controlUiImportPattern,
|
|
11
|
+
paintProperty,
|
|
12
|
+
rawTypeClass,
|
|
13
|
+
rawTypeMessage,
|
|
14
|
+
} from "./class-policy.mjs";
|
|
2
15
|
|
|
3
16
|
function gritRegex(pattern) {
|
|
4
17
|
return `r"(?s).*(?:${pattern.replaceAll('"', '\\"')}).*"`;
|
|
5
18
|
}
|
|
6
19
|
|
|
20
|
+
function gritExactRegex(pattern) {
|
|
21
|
+
return `r"${pattern.replaceAll('"', '\\"')}"`;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
function gritString(text) {
|
|
8
25
|
return JSON.stringify(text);
|
|
9
26
|
}
|
|
10
27
|
|
|
11
|
-
const stringTexts =
|
|
28
|
+
const stringTexts = (name) =>
|
|
29
|
+
`or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $${name}Member where { $${name}Member <: r"(?s)[\\"'].*" } }`;
|
|
12
30
|
const classLiterals = "or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }";
|
|
13
31
|
const renderedTag = "or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }";
|
|
32
|
+
const componentName = `r"([A-Z]\\w*)(?:\\.[\\w.]+)?"`;
|
|
14
33
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
$value <: or { ${classLiterals}, TsAsExpression(expression=${classLiterals}), TsSatisfiesExpression(expression=${classLiterals}) },
|
|
24
|
-
$value <: contains ${stringTexts} as $constText where { $constText <: ${styled} }
|
|
34
|
+
function constantMatches(reference, literals, offends) {
|
|
35
|
+
return `contains JsReferenceIdentifier() as $${reference} where {
|
|
36
|
+
$${reference} <: r"(.+)"($${reference}Name),
|
|
37
|
+
$module <: contains JsVariableDeclaration(declarators=$${reference}Declarators) as $${reference}Declaration where {
|
|
38
|
+
$${reference}Declaration <: r"(?s)const\\s.*",
|
|
39
|
+
$${reference}Declarators <: some JsVariableDeclarator(id=$${reference}Name, initializer=JsInitializerClause(expression=$${reference}Value)),
|
|
40
|
+
$${reference}Value <: or { ${literals}, TsAsExpression(expression=${literals}), TsSatisfiesExpression(expression=${literals}) },
|
|
41
|
+
$${reference}Value <: ${offends}
|
|
25
42
|
}
|
|
26
|
-
}
|
|
43
|
+
}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function classNameMatches(attribute, pattern) {
|
|
47
|
+
const offending = (prefix) => `contains ${stringTexts(prefix)} as $${prefix}Text where { $${prefix}Text <: ${gritRegex(pattern)} }`;
|
|
48
|
+
const name = attribute.slice(1);
|
|
49
|
+
return `${attribute} <: or {
|
|
50
|
+
${offending(name)},
|
|
51
|
+
${constantMatches(`${name}Constant`, classLiterals, offending(`${name}Constant`))}
|
|
27
52
|
}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function styleMatches(attribute) {
|
|
56
|
+
const offending = (prefix) =>
|
|
57
|
+
`contains or { JsPropertyObjectMember(name=$${prefix}Key), JsShorthandPropertyObjectMember(name=$${prefix}Key) } where { $${prefix}Key <: ${gritExactRegex(componentStyleKey)} }`;
|
|
58
|
+
const name = attribute.slice(1);
|
|
59
|
+
return `${attribute} <: or {
|
|
60
|
+
${offending(name)},
|
|
61
|
+
${constantMatches(`${name}Constant`, "JsObjectExpression()", offending(`${name}Constant`))}
|
|
62
|
+
}`;
|
|
63
|
+
}
|
|
28
64
|
|
|
29
65
|
function importedFromControlUi(component, importSources, suffix = "") {
|
|
30
66
|
return `$module <: contains JsImport() as $import${suffix} where {
|
|
@@ -33,47 +69,100 @@ function importedFromControlUi(component, importSources, suffix = "") {
|
|
|
33
69
|
}`;
|
|
34
70
|
}
|
|
35
71
|
|
|
72
|
+
function componentAttributePattern({ attribute, offends, message, importSources }) {
|
|
73
|
+
return `or {
|
|
74
|
+
or {
|
|
75
|
+
JsxSelfClosingElement(name=$name, attributes=$attributes),
|
|
76
|
+
JsxOpeningElement(name=$name, attributes=$attributes)
|
|
77
|
+
} where {
|
|
78
|
+
$attributes <: some JsxAttribute(name=\`${attribute}\`) as $attr
|
|
79
|
+
},
|
|
80
|
+
JsxAttribute(name=\`render\`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
|
|
81
|
+
$rendered <: or {
|
|
82
|
+
JsxTagExpression(tag=$tag),
|
|
83
|
+
JsArrowFunctionExpression(body=or {
|
|
84
|
+
${renderedTag},
|
|
85
|
+
JsFunctionBody(statements=some JsReturnStatement(argument=${renderedTag}))
|
|
86
|
+
})
|
|
87
|
+
},
|
|
88
|
+
$tag <: or {
|
|
89
|
+
JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
|
|
90
|
+
JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
|
|
91
|
+
},
|
|
92
|
+
$renderedAttributes <: some JsxAttribute(name=\`${attribute}\`) as $attr,
|
|
93
|
+
$render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
|
|
94
|
+
$render <: within JsModule() as $module,
|
|
95
|
+
$renderedName <: r"([^.]+).*"($renderedComponent),
|
|
96
|
+
not ${importedFromControlUi("$renderedComponent", importSources, "Rendered")}
|
|
97
|
+
}
|
|
98
|
+
} where {
|
|
99
|
+
$name <: ${componentName}($component),
|
|
100
|
+
$attr <: within JsModule() as $module,
|
|
101
|
+
${offends("$attr")},
|
|
102
|
+
${importedFromControlUi("$component", importSources)},
|
|
103
|
+
register_diagnostic(span=$attr, message=${gritString(message)}, severity="error")
|
|
104
|
+
}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function partClassPattern(importSources) {
|
|
108
|
+
return `or {
|
|
109
|
+
JsxSelfClosingElement(name=$plainName, attributes=$plainAttributes),
|
|
110
|
+
JsxOpeningElement(name=$plainName, attributes=$plainAttributes)
|
|
111
|
+
} where {
|
|
112
|
+
$plainAttributes <: some JsxAttribute(name=\`className\`) as $plainAttr,
|
|
113
|
+
not $plainAttr <: within JsxAttribute(name=\`render\`),
|
|
114
|
+
$plainAttr <: within JsModule() as $module,
|
|
115
|
+
${classNameMatches("$plainAttr", componentPartClass)},
|
|
116
|
+
not and {
|
|
117
|
+
$plainName <: ${componentName}($plainComponent),
|
|
118
|
+
${importedFromControlUi("$plainComponent", importSources, "Plain")}
|
|
119
|
+
},
|
|
120
|
+
register_diagnostic(span=$plainAttr, message=${gritString(componentPartMessage)}, severity="error")
|
|
121
|
+
}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const partSubject = gritExactRegex(componentPartSelector);
|
|
125
|
+
const enclosingRule = (selectors) => `or { CssQualifiedRule(prelude=${selectors}), CssNestedQualifiedRule(prelude=${selectors}) }`;
|
|
126
|
+
|
|
36
127
|
export function gritRules(importSources = []) {
|
|
37
128
|
return {
|
|
38
129
|
"no-component-style-classes.grit": `language js(typescript, jsx)
|
|
39
130
|
|
|
40
131
|
or {
|
|
132
|
+
${componentAttributePattern({
|
|
133
|
+
attribute: "className",
|
|
134
|
+
offends: (attribute) => classNameMatches(attribute, componentStyleClass),
|
|
135
|
+
message: componentStyleMessage,
|
|
136
|
+
importSources,
|
|
137
|
+
})},
|
|
138
|
+
${partClassPattern(importSources)}
|
|
139
|
+
}
|
|
140
|
+
`,
|
|
141
|
+
"no-component-style-props.grit": `language js(typescript, jsx)
|
|
142
|
+
|
|
143
|
+
${componentAttributePattern({ attribute: "style", offends: styleMatches, message: componentStylePropMessage, importSources })}
|
|
144
|
+
`,
|
|
145
|
+
"no-component-part-css.grit": `language css
|
|
146
|
+
|
|
147
|
+
or {
|
|
148
|
+
CssGenericProperty(name=$property) as $paint where { $property <: ${gritExactRegex(`(?i)${paintProperty}`)} },
|
|
149
|
+
CssAtRule() as $paint where { $paint <: r"(?s)@apply\\s.*", $paint <: ${gritRegex(componentStyleClass)} }
|
|
150
|
+
} where {
|
|
151
|
+
$paint <: within ${enclosingRule("$selectors")} as $rule,
|
|
41
152
|
or {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
153
|
+
$selectors <: some ${partSubject},
|
|
154
|
+
and {
|
|
155
|
+
$selectors <: some r"\\s*&[^\\s>+~]*\\s*",
|
|
156
|
+
$rule <: within ${enclosingRule("$outer")} where { $outer <: some ${partSubject} }
|
|
157
|
+
}
|
|
46
158
|
},
|
|
47
|
-
|
|
48
|
-
$rendered <: or {
|
|
49
|
-
JsxTagExpression(tag=$tag),
|
|
50
|
-
JsArrowFunctionExpression(body=or {
|
|
51
|
-
${renderedTag},
|
|
52
|
-
JsFunctionBody(statements=some JsReturnStatement(argument=${renderedTag}))
|
|
53
|
-
})
|
|
54
|
-
},
|
|
55
|
-
$tag <: or {
|
|
56
|
-
JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
|
|
57
|
-
JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
|
|
58
|
-
},
|
|
59
|
-
$renderedAttributes <: some JsxAttribute(name=\`className\`) as $attr,
|
|
60
|
-
$render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
|
|
61
|
-
$render <: within JsModule() as $module,
|
|
62
|
-
$renderedName <: r"([^.]+).*"($renderedComponent),
|
|
63
|
-
not ${importedFromControlUi("$renderedComponent", importSources, "Rendered")}
|
|
64
|
-
}
|
|
65
|
-
} where {
|
|
66
|
-
$name <: r"([A-Z]\\w*)(?:\\.[\\w.]+)?"($component),
|
|
67
|
-
$attr <: within JsModule() as $module,
|
|
68
|
-
${styledClassName},
|
|
69
|
-
${importedFromControlUi("$component", importSources)},
|
|
70
|
-
register_diagnostic(span=$attr, message=${gritString(componentStyleMessage)}, severity="error")
|
|
159
|
+
register_diagnostic(span=$paint, message=${gritString(componentPartCssMessage)}, severity="error")
|
|
71
160
|
}
|
|
72
161
|
`,
|
|
73
162
|
"no-raw-type-classes.grit": `language js(typescript, jsx)
|
|
74
163
|
|
|
75
164
|
JsxAttribute(name=\`className\`) as $attr where {
|
|
76
|
-
$attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(rawTypeClass)} },
|
|
165
|
+
$attr <: contains ${stringTexts("attr")} as $text where { $text <: ${gritRegex(rawTypeClass)} },
|
|
77
166
|
register_diagnostic(span=$attr, message=${gritString(rawTypeMessage)}, severity="error")
|
|
78
167
|
}
|
|
79
168
|
`,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
language css
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
CssGenericProperty(name=$property) as $paint where { $property <: r"(?i)(?:padding|background|color|font|line-height|letter-spacing|border|outline|box-shadow|text-shadow|text-decoration|text-underline|text-transform|opacity|filter|backdrop-filter|fill|stroke|caret-color|accent-color|mask|-webkit-mask|-webkit-font-smoothing|-moz-osx-font-smoothing)(?:-[\w-]*)?" },
|
|
5
|
+
CssAtRule() as $paint where { $paint <: r"(?s)@apply\s.*", $paint <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:(?:[\s\"'\x60]|$)|[^lcrjsewnbp\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60]))))|c(?:(?:[\s\"'\x60]|$)|[^el\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:[^\s\"'\x60])))))|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|r(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^g\s\"'\x60]|g(?:(?:[\s\"'\x60]|$)|[^h\s\"'\x60]|h(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|j(?:(?:[\s\"'\x60]|$)|[^u\s\"'\x60]|u(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|e(?:(?:[\s\"'\x60]|$)|[^nl\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^d\s\"'\x60]|d(?:[^\s\"'\x60]))|l(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:[^\s\"'\x60]))))))))|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|n(?:(?:[\s\"'\x60]|$)|[^o\s\"'\x60]|o(?:(?:[\s\"'\x60]|$)|[^w\s\"'\x60]|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))))|b(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^c\s\"'\x60]|c(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:[^\s\"'\x60])))))))|p(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|blur(?:-|(?:[\s\"'\x60]|$))|brightness-|contrast-[\d\[]|grayscale|hue-rotate-|invert|saturate-|sepia|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)|(?:^|[^\w-])\[(?:padding|background|color|font|line-height|letter-spacing|border|outline|box-shadow|text-shadow|text-decoration|text-underline|text-transform|opacity|filter|backdrop-filter|fill|stroke|caret-color|accent-color|mask|-webkit-mask|-webkit-font-smoothing|-moz-osx-font-smoothing)(?:-[\w-]*)?:).*" }
|
|
6
|
+
} where {
|
|
7
|
+
$paint <: within or { CssQualifiedRule(prelude=$selectors), CssNestedQualifiedRule(prelude=$selectors) } as $rule,
|
|
8
|
+
or {
|
|
9
|
+
$selectors <: some r"(?s).*\[data-(?:control|slot)\b[^\s>+~]*\s*",
|
|
10
|
+
and {
|
|
11
|
+
$selectors <: some r"\s*&[^\s>+~]*\s*",
|
|
12
|
+
$rule <: within or { CssQualifiedRule(prelude=$outer), CssNestedQualifiedRule(prelude=$outer) } where { $outer <: some r"(?s).*\[data-(?:control|slot)\b[^\s>+~]*\s*" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
register_diagnostic(span=$paint, message="Stylesheet paints a Control UI part: color, type, padding, borders, radius, shadows and opacity belong to the component recipe. Set the part's --cui-* knobs here instead, or use the component's props.", severity="error")
|
|
16
|
+
}
|
|
@@ -2,50 +2,80 @@ language js(typescript, jsx)
|
|
|
2
2
|
|
|
3
3
|
or {
|
|
4
4
|
or {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
JsxAttribute(name=`render`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
|
|
11
|
-
$rendered <: or {
|
|
12
|
-
JsxTagExpression(tag=$tag),
|
|
13
|
-
JsArrowFunctionExpression(body=or {
|
|
14
|
-
or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) },
|
|
15
|
-
JsFunctionBody(statements=some JsReturnStatement(argument=or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }))
|
|
16
|
-
})
|
|
5
|
+
or {
|
|
6
|
+
JsxSelfClosingElement(name=$name, attributes=$attributes),
|
|
7
|
+
JsxOpeningElement(name=$name, attributes=$attributes)
|
|
8
|
+
} where {
|
|
9
|
+
$attributes <: some JsxAttribute(name=`className`) as $attr
|
|
17
10
|
},
|
|
18
|
-
$
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
JsxAttribute(name=`render`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
|
|
12
|
+
$rendered <: or {
|
|
13
|
+
JsxTagExpression(tag=$tag),
|
|
14
|
+
JsArrowFunctionExpression(body=or {
|
|
15
|
+
or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) },
|
|
16
|
+
JsFunctionBody(statements=some JsReturnStatement(argument=or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }))
|
|
17
|
+
})
|
|
18
|
+
},
|
|
19
|
+
$tag <: or {
|
|
20
|
+
JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
|
|
21
|
+
JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
|
|
22
|
+
},
|
|
23
|
+
$renderedAttributes <: some JsxAttribute(name=`className`) as $attr,
|
|
24
|
+
$render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
|
|
25
|
+
$render <: within JsModule() as $module,
|
|
26
|
+
$renderedName <: r"([^.]+).*"($renderedComponent),
|
|
27
|
+
not $module <: contains JsImport() as $importRendered where {
|
|
27
28
|
$importRendered <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
|
|
28
29
|
$importRendered <: contains JsIdentifierBinding() as $bindingRendered where { $bindingRendered <: $renderedComponent }
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
} where {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $
|
|
36
|
-
contains JsReferenceIdentifier() as $
|
|
37
|
-
$
|
|
38
|
-
$module <: contains JsVariableDeclaration(declarators=$
|
|
39
|
-
$
|
|
40
|
-
$
|
|
41
|
-
$
|
|
42
|
-
$
|
|
31
|
+
}
|
|
32
|
+
} where {
|
|
33
|
+
$name <: r"([A-Z]\w*)(?:\.[\w.]+)?"($component),
|
|
34
|
+
$attr <: within JsModule() as $module,
|
|
35
|
+
$attr <: or {
|
|
36
|
+
contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $attrMember where { $attrMember <: r"(?s)[\"'].*" } } as $attrText where { $attrText <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:(?:[\s\"'\x60]|$)|[^lcrjsewnbp\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60]))))|c(?:(?:[\s\"'\x60]|$)|[^el\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:[^\s\"'\x60])))))|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|r(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^g\s\"'\x60]|g(?:(?:[\s\"'\x60]|$)|[^h\s\"'\x60]|h(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|j(?:(?:[\s\"'\x60]|$)|[^u\s\"'\x60]|u(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|e(?:(?:[\s\"'\x60]|$)|[^nl\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^d\s\"'\x60]|d(?:[^\s\"'\x60]))|l(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:[^\s\"'\x60]))))))))|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|n(?:(?:[\s\"'\x60]|$)|[^o\s\"'\x60]|o(?:(?:[\s\"'\x60]|$)|[^w\s\"'\x60]|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))))|b(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^c\s\"'\x60]|c(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:[^\s\"'\x60])))))))|p(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|blur(?:-|(?:[\s\"'\x60]|$))|brightness-|contrast-[\d\[]|grayscale|hue-rotate-|invert|saturate-|sepia|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)|(?:^|[^\w-])\[(?:padding|background|color|font|line-height|letter-spacing|border|outline|box-shadow|text-shadow|text-decoration|text-underline|text-transform|opacity|filter|backdrop-filter|fill|stroke|caret-color|accent-color|mask|-webkit-mask|-webkit-font-smoothing|-moz-osx-font-smoothing)(?:-[\w-]*)?:).*" },
|
|
37
|
+
contains JsReferenceIdentifier() as $attrConstant where {
|
|
38
|
+
$attrConstant <: r"(.+)"($attrConstantName),
|
|
39
|
+
$module <: contains JsVariableDeclaration(declarators=$attrConstantDeclarators) as $attrConstantDeclaration where {
|
|
40
|
+
$attrConstantDeclaration <: r"(?s)const\s.*",
|
|
41
|
+
$attrConstantDeclarators <: some JsVariableDeclarator(id=$attrConstantName, initializer=JsInitializerClause(expression=$attrConstantValue)),
|
|
42
|
+
$attrConstantValue <: or { or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }, TsAsExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }), TsSatisfiesExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }) },
|
|
43
|
+
$attrConstantValue <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $attrConstantMember where { $attrConstantMember <: r"(?s)[\"'].*" } } as $attrConstantText where { $attrConstantText <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:(?:[\s\"'\x60]|$)|[^lcrjsewnbp\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60]))))|c(?:(?:[\s\"'\x60]|$)|[^el\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:[^\s\"'\x60])))))|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|r(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^g\s\"'\x60]|g(?:(?:[\s\"'\x60]|$)|[^h\s\"'\x60]|h(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|j(?:(?:[\s\"'\x60]|$)|[^u\s\"'\x60]|u(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|e(?:(?:[\s\"'\x60]|$)|[^nl\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^d\s\"'\x60]|d(?:[^\s\"'\x60]))|l(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:[^\s\"'\x60]))))))))|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|n(?:(?:[\s\"'\x60]|$)|[^o\s\"'\x60]|o(?:(?:[\s\"'\x60]|$)|[^w\s\"'\x60]|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))))|b(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^c\s\"'\x60]|c(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:[^\s\"'\x60])))))))|p(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|blur(?:-|(?:[\s\"'\x60]|$))|brightness-|contrast-[\d\[]|grayscale|hue-rotate-|invert|saturate-|sepia|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)|(?:^|[^\w-])\[(?:padding|background|color|font|line-height|letter-spacing|border|outline|box-shadow|text-shadow|text-decoration|text-underline|text-transform|opacity|filter|backdrop-filter|fill|stroke|caret-color|accent-color|mask|-webkit-mask|-webkit-font-smoothing|-moz-osx-font-smoothing)(?:-[\w-]*)?:).*" }
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
},
|
|
46
|
-
|
|
47
|
+
$module <: contains JsImport() as $import where {
|
|
47
48
|
$import <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
|
|
48
49
|
$import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
|
|
49
50
|
},
|
|
50
|
-
|
|
51
|
+
register_diagnostic(span=$attr, message="Control UI component styled through className: color, type, padding, borders, radius, shadows, state styles and !important belong to the component. Use its props or a --cui-* knob; keep className for layout (flex, gap, width, margin, placement).", severity="error")
|
|
52
|
+
},
|
|
53
|
+
or {
|
|
54
|
+
JsxSelfClosingElement(name=$plainName, attributes=$plainAttributes),
|
|
55
|
+
JsxOpeningElement(name=$plainName, attributes=$plainAttributes)
|
|
56
|
+
} where {
|
|
57
|
+
$plainAttributes <: some JsxAttribute(name=`className`) as $plainAttr,
|
|
58
|
+
not $plainAttr <: within JsxAttribute(name=`render`),
|
|
59
|
+
$plainAttr <: within JsModule() as $module,
|
|
60
|
+
$plainAttr <: or {
|
|
61
|
+
contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $plainAttrMember where { $plainAttrMember <: r"(?s)[\"'].*" } } as $plainAttrText where { $plainAttrText <: r"(?s).*(?:\[&[^\s\"'\x60]*?data-(?:control|slot)\b|(?:^|[\s\"'\x60:])\*{1,2}:data-\[(?:control|slot)\b).*" },
|
|
62
|
+
contains JsReferenceIdentifier() as $plainAttrConstant where {
|
|
63
|
+
$plainAttrConstant <: r"(.+)"($plainAttrConstantName),
|
|
64
|
+
$module <: contains JsVariableDeclaration(declarators=$plainAttrConstantDeclarators) as $plainAttrConstantDeclaration where {
|
|
65
|
+
$plainAttrConstantDeclaration <: r"(?s)const\s.*",
|
|
66
|
+
$plainAttrConstantDeclarators <: some JsVariableDeclarator(id=$plainAttrConstantName, initializer=JsInitializerClause(expression=$plainAttrConstantValue)),
|
|
67
|
+
$plainAttrConstantValue <: or { or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }, TsAsExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }), TsSatisfiesExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }) },
|
|
68
|
+
$plainAttrConstantValue <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $plainAttrConstantMember where { $plainAttrConstantMember <: r"(?s)[\"'].*" } } as $plainAttrConstantText where { $plainAttrConstantText <: r"(?s).*(?:\[&[^\s\"'\x60]*?data-(?:control|slot)\b|(?:^|[\s\"'\x60:])\*{1,2}:data-\[(?:control|slot)\b).*" }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
not and {
|
|
73
|
+
$plainName <: r"([A-Z]\w*)(?:\.[\w.]+)?"($plainComponent),
|
|
74
|
+
$module <: contains JsImport() as $importPlain where {
|
|
75
|
+
$importPlain <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
|
|
76
|
+
$importPlain <: contains JsIdentifierBinding() as $bindingPlain where { $bindingPlain <: $plainComponent }
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
register_diagnostic(span=$plainAttr, message="className styles a Control UI component's parts from outside through a data-slot or data-control selector. The component owns its parts: use its props, compose its parts, or set a --cui-* knob on it.", severity="error")
|
|
80
|
+
}
|
|
51
81
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
language js(typescript, jsx)
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
or {
|
|
5
|
+
JsxSelfClosingElement(name=$name, attributes=$attributes),
|
|
6
|
+
JsxOpeningElement(name=$name, attributes=$attributes)
|
|
7
|
+
} where {
|
|
8
|
+
$attributes <: some JsxAttribute(name=`style`) as $attr
|
|
9
|
+
},
|
|
10
|
+
JsxAttribute(name=`render`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
|
|
11
|
+
$rendered <: or {
|
|
12
|
+
JsxTagExpression(tag=$tag),
|
|
13
|
+
JsArrowFunctionExpression(body=or {
|
|
14
|
+
or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) },
|
|
15
|
+
JsFunctionBody(statements=some JsReturnStatement(argument=or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }))
|
|
16
|
+
})
|
|
17
|
+
},
|
|
18
|
+
$tag <: or {
|
|
19
|
+
JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
|
|
20
|
+
JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
|
|
21
|
+
},
|
|
22
|
+
$renderedAttributes <: some JsxAttribute(name=`style`) as $attr,
|
|
23
|
+
$render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
|
|
24
|
+
$render <: within JsModule() as $module,
|
|
25
|
+
$renderedName <: r"([^.]+).*"($renderedComponent),
|
|
26
|
+
not $module <: contains JsImport() as $importRendered where {
|
|
27
|
+
$importRendered <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
|
|
28
|
+
$importRendered <: contains JsIdentifierBinding() as $bindingRendered where { $bindingRendered <: $renderedComponent }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} where {
|
|
32
|
+
$name <: r"([A-Z]\w*)(?:\.[\w.]+)?"($component),
|
|
33
|
+
$attr <: within JsModule() as $module,
|
|
34
|
+
$attr <: or {
|
|
35
|
+
contains or { JsPropertyObjectMember(name=$attrKey), JsShorthandPropertyObjectMember(name=$attrKey) } where { $attrKey <: r"[\"']?(?:(?:padding|background|color|font|lineHeight|letterSpacing|border|outline|boxShadow|textShadow|textDecoration|textUnderline|textTransform|opacity|filter|backdropFilter|fill|stroke|caretColor|accentColor|mask|WebkitMask|WebkitFontSmoothing|MozOsxFontSmoothing)(?:[A-Z]\w*)?|(?:padding|background|color|font|line-height|letter-spacing|border|outline|box-shadow|text-shadow|text-decoration|text-underline|text-transform|opacity|filter|backdrop-filter|fill|stroke|caret-color|accent-color|mask|-webkit-mask|-webkit-font-smoothing|-moz-osx-font-smoothing)(?:-[\w-]*)?)[\"']?" },
|
|
36
|
+
contains JsReferenceIdentifier() as $attrConstant where {
|
|
37
|
+
$attrConstant <: r"(.+)"($attrConstantName),
|
|
38
|
+
$module <: contains JsVariableDeclaration(declarators=$attrConstantDeclarators) as $attrConstantDeclaration where {
|
|
39
|
+
$attrConstantDeclaration <: r"(?s)const\s.*",
|
|
40
|
+
$attrConstantDeclarators <: some JsVariableDeclarator(id=$attrConstantName, initializer=JsInitializerClause(expression=$attrConstantValue)),
|
|
41
|
+
$attrConstantValue <: or { JsObjectExpression(), TsAsExpression(expression=JsObjectExpression()), TsSatisfiesExpression(expression=JsObjectExpression()) },
|
|
42
|
+
$attrConstantValue <: contains or { JsPropertyObjectMember(name=$attrConstantKey), JsShorthandPropertyObjectMember(name=$attrConstantKey) } where { $attrConstantKey <: r"[\"']?(?:(?:padding|background|color|font|lineHeight|letterSpacing|border|outline|boxShadow|textShadow|textDecoration|textUnderline|textTransform|opacity|filter|backdropFilter|fill|stroke|caretColor|accentColor|mask|WebkitMask|WebkitFontSmoothing|MozOsxFontSmoothing)(?:[A-Z]\w*)?|(?:padding|background|color|font|line-height|letter-spacing|border|outline|box-shadow|text-shadow|text-decoration|text-underline|text-transform|opacity|filter|backdrop-filter|fill|stroke|caret-color|accent-color|mask|-webkit-mask|-webkit-font-smoothing|-moz-osx-font-smoothing)(?:-[\w-]*)?)[\"']?" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
$module <: contains JsImport() as $import where {
|
|
47
|
+
$import <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
|
|
48
|
+
$import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
|
|
49
|
+
},
|
|
50
|
+
register_diagnostic(span=$attr, message="Control UI component painted through style: color, type, padding, borders, radius, shadows and opacity belong to the component. Set a --cui-* knob in style or use its props; keep other style keys for layout.", severity="error")
|
|
51
|
+
}
|