@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.
Files changed (51) hide show
  1. package/README.md +20 -7
  2. package/dist/blocks/design-canvas.js +16 -10
  3. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  4. package/dist/knob-contracts/field-knobs.js +1 -0
  5. package/dist/knob-contracts/gradient-editor-knobs.d.ts +1 -1
  6. package/dist/knob-contracts/gradient-editor-knobs.js +1 -4
  7. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  8. package/dist/knob-contracts/popup-knobs.js +2 -0
  9. package/dist/lib/phone-input-format.js +1 -1
  10. package/dist/styles/recipes/card.css +8 -0
  11. package/dist/styles/recipes/color-picker-values.css +4 -18
  12. package/dist/styles/recipes/color-picker.css +3 -4
  13. package/dist/styles/recipes/field-parts.css +8 -1
  14. package/dist/styles/recipes/gradient-editor.css +3 -47
  15. package/dist/styles/recipes/markdown.css +5 -1
  16. package/dist/styles/recipes/popup-lists.css +17 -0
  17. package/dist/styles/recipes/popup.css +3 -0
  18. package/dist/styles/theme.css +4 -0
  19. package/dist/ui/autocomplete.d.ts +3 -0
  20. package/dist/ui/autocomplete.js +17 -2
  21. package/dist/ui/chart-cartesian.d.ts +60 -57
  22. package/dist/ui/chart-cartesian.js +7 -7
  23. package/dist/ui/chart-colors.d.ts +4 -2
  24. package/dist/ui/chart-colors.js +6 -2
  25. package/dist/ui/chart-marks.d.ts +4 -4
  26. package/dist/ui/chart-marks.js +14 -5
  27. package/dist/ui/chart-series.d.ts +9 -6
  28. package/dist/ui/chart-series.js +3 -2
  29. package/dist/ui/color-picker-swatches.d.ts +4 -3
  30. package/dist/ui/color-picker-swatches.js +7 -8
  31. package/dist/ui/command.d.ts +12 -0
  32. package/dist/ui/command.js +31 -1
  33. package/dist/ui/field.js +1 -1
  34. package/dist/ui/gradient-editor.d.ts +3 -2
  35. package/dist/ui/gradient-editor.js +7 -8
  36. package/dist/ui/markdown-components.d.ts +27 -0
  37. package/dist/ui/markdown-components.js +57 -0
  38. package/dist/ui/markdown-elements.d.ts +6 -25
  39. package/dist/ui/markdown-elements.js +19 -53
  40. package/dist/ui/markdown.d.ts +2 -7
  41. package/dist/ui/markdown.js +3 -20
  42. package/dist/ui/phone-input.js +2 -2
  43. package/lint/biome-preset.json +8 -1
  44. package/lint/class-policy.mjs +50 -1
  45. package/lint/eslint-plugin.mjs +98 -49
  46. package/lint/grit-rules.mjs +131 -42
  47. package/lint/no-component-part-css.grit +16 -0
  48. package/lint/no-component-style-classes.grit +66 -36
  49. package/lint/no-component-style-props.grit +51 -0
  50. package/lint/no-raw-type-classes.grit +1 -1
  51. package/package.json +5 -1
@@ -1,6 +1,18 @@
1
- import { componentStyleClass, componentStyleMessage, controlUiImportPattern, rawTypeClass, rawTypeMessage } from "./class-policy.mjs";
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 classLiteral(init) {
45
+ function literalOf(init, types) {
26
46
  const value = init?.type === "TSAsExpression" || init?.type === "TSSatisfiesExpression" ? init.expression : init;
27
- return classLiteralTypes.has(value?.type) ? value : undefined;
47
+ return types.has(value?.type) ? value : undefined;
28
48
  }
29
49
 
30
- function isClassName(attribute) {
31
- return attribute.name.name === "className" && Boolean(attribute.value);
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 isClassName(attribute) && stringsIn(attribute.value).some((text) => pattern.test(text));
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
- const noComponentStyleClasses = {
63
- meta: {
64
- type: "problem",
65
- docs: { description: "Keep className on Control UI components to layout; the component owns its look." },
66
- messages: { componentStyle: componentStyleMessage },
67
- schema: [
68
- {
69
- type: "object",
70
- properties: { importSources: { type: "array", items: { type: "string" } } },
71
- additionalProperties: false,
72
- },
73
- ],
74
- },
75
- create(context) {
76
- const controlUiImport = new RegExp(`^${controlUiImportPattern(context.options[0]?.importSources)}`);
77
- let controlUiNames = new Set();
78
- const isControlUi = (element) => element !== undefined && controlUiNames.has(elementRootName(element.name));
79
- const constantClasses = new Map();
80
- const componentClassNames = [];
81
- return {
82
- Program(program) {
83
- controlUiNames = controlUiLocalNames(program, controlUiImport);
84
- },
85
- VariableDeclarator(declarator) {
86
- const literal = classLiteral(declarator.init);
87
- if (declarator.parent.kind !== "const" || declarator.id.type !== "Identifier" || !literal) return;
88
- constantClasses.set(declarator.id.name, [...(constantClasses.get(declarator.id.name) ?? []), ...stringsIn(literal)]);
89
- },
90
- JSXAttribute(attribute) {
91
- if (!isClassName(attribute)) return;
92
- const element = attribute.parent;
93
- if (isControlUi(element) || isControlUi(renderPropOwner(element.parent))) componentClassNames.push(attribute);
94
- },
95
- "Program:exit"() {
96
- for (const attribute of componentClassNames) {
97
- const constants = referencedNames(attribute.value).flatMap((name) => constantClasses.get(name) ?? []);
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
  };
@@ -1,30 +1,66 @@
1
- import { componentStyleClass, componentStyleMessage, controlUiImportPattern, rawTypeClass, rawTypeMessage } from "./class-policy.mjs";
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 = `or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\\"'].*" } }`;
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
- const styled = gritRegex(componentStyleClass);
16
- const styledClassName = `$attr <: or {
17
- contains ${stringTexts} as $text where { $text <: ${styled} },
18
- contains JsReferenceIdentifier() as $reference where {
19
- $reference <: r"(.+)"($referenced),
20
- $module <: contains JsVariableDeclaration(declarators=$declarators) as $declaration where {
21
- $declaration <: r"(?s)const\\s.*",
22
- $declarators <: some JsVariableDeclarator(id=$referenced, initializer=JsInitializerClause(expression=$value)),
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
- JsxSelfClosingElement(name=$name, attributes=$attributes),
43
- JsxOpeningElement(name=$name, attributes=$attributes)
44
- } where {
45
- $attributes <: some JsxAttribute(name=\`className\`) as $attr
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
- JsxAttribute(name=\`render\`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
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
- JsxSelfClosingElement(name=$name, attributes=$attributes),
6
- JsxOpeningElement(name=$name, attributes=$attributes)
7
- } where {
8
- $attributes <: some JsxAttribute(name=`className`) 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
- })
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
- $tag <: or {
19
- JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
20
- JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
21
- },
22
- $renderedAttributes <: some JsxAttribute(name=`className`) 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 {
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
- $name <: r"([A-Z]\w*)(?:\.[\w.]+)?"($component),
33
- $attr <: within JsModule() as $module,
34
- $attr <: or {
35
- contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\"'].*" } } as $text where { $text <: 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|(?: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]*?\]):)).*" },
36
- contains JsReferenceIdentifier() as $reference where {
37
- $reference <: r"(.+)"($referenced),
38
- $module <: contains JsVariableDeclaration(declarators=$declarators) as $declaration where {
39
- $declaration <: r"(?s)const\s.*",
40
- $declarators <: some JsVariableDeclarator(id=$referenced, initializer=JsInitializerClause(expression=$value)),
41
- $value <: or { or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }, TsAsExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }), TsSatisfiesExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }) },
42
- $value <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\"'].*" } } as $constText where { $constText <: 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|(?: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]*?\]):)).*" }
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
- $module <: contains JsImport() as $import where {
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
- 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")
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
+ }