@clearstory/drywall-react 7.1.0 → 7.2.1

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.
@@ -1,9 +1,13 @@
1
- import e from "./no-system-props.js";
2
- import l from "./prefer-vars-palette.js";
1
+ import e from "./no-shorthand-spacing.js";
2
+ import l from "./no-system-props.js";
3
+ import a from "./prefer-logical-properties.js";
4
+ import o from "./prefer-vars-palette.js";
3
5
  const r = {
4
6
  rules: {
5
- "no-system-props": e,
6
- "prefer-vars-palette": l
7
+ "no-shorthand-spacing": e,
8
+ "no-system-props": l,
9
+ "prefer-logical-properties": a,
10
+ "prefer-vars-palette": o
7
11
  },
8
12
  configs: {}
9
13
  };
@@ -13,21 +17,25 @@ r.configs.recommended = {
13
17
  "@clearstory/drywall-react": r
14
18
  },
15
19
  rules: {
20
+ "@clearstory/drywall-react/no-shorthand-spacing": "error",
16
21
  "@clearstory/drywall-react/no-system-props": "error",
22
+ "@clearstory/drywall-react/prefer-logical-properties": "error",
17
23
  "@clearstory/drywall-react/prefer-vars-palette": "error"
18
24
  }
19
25
  };
20
- const t = {
26
+ const y = {
21
27
  name: "@clearstory/drywall-react",
22
28
  plugins: {
23
29
  "@clearstory/drywall-react": r
24
30
  },
25
31
  rules: {
32
+ "@clearstory/drywall-react/no-shorthand-spacing": "error",
26
33
  "@clearstory/drywall-react/no-system-props": "error",
34
+ "@clearstory/drywall-react/prefer-logical-properties": "error",
27
35
  "@clearstory/drywall-react/prefer-vars-palette": "error"
28
36
  }
29
37
  };
30
38
  export {
31
- t as default,
39
+ y as default,
32
40
  r as plugin
33
41
  };
@@ -0,0 +1,3 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,57 @@
1
+ import { createSxVisitor as i } from "./sx-utils.js";
2
+ const s = {
3
+ p: "padding",
4
+ pt: "paddingTop",
5
+ pr: "paddingRight",
6
+ pb: "paddingBottom",
7
+ pl: "paddingLeft",
8
+ px: "paddingX",
9
+ py: "paddingY",
10
+ m: "margin",
11
+ mt: "marginTop",
12
+ mr: "marginRight",
13
+ mb: "marginBottom",
14
+ ml: "marginLeft",
15
+ mx: "marginX",
16
+ my: "marginY"
17
+ }, g = {
18
+ meta: {
19
+ type: "suggestion",
20
+ fixable: "code",
21
+ docs: {
22
+ description: "Disallow MUI spacing shorthands (p, pt, m, mx, etc.) in sx props",
23
+ category: "Best Practices",
24
+ recommended: !0
25
+ },
26
+ messages: {
27
+ shorthandSpacing: 'Use "{{replacement}}" instead of shorthand "{{property}}" in sx for readability.'
28
+ },
29
+ schema: []
30
+ },
31
+ create(a) {
32
+ return i({
33
+ onProperty(n, e) {
34
+ const t = s[n];
35
+ t && a.report({
36
+ node: e,
37
+ messageId: "shorthandSpacing",
38
+ data: { property: n, replacement: t },
39
+ fix(p) {
40
+ const r = e;
41
+ if (e.type === "Literal") {
42
+ const o = a.sourceCode.getText(r)[0];
43
+ return p.replaceText(
44
+ r,
45
+ `${o}${t}${o}`
46
+ );
47
+ }
48
+ return p.replaceText(r, t);
49
+ }
50
+ });
51
+ }
52
+ });
53
+ }
54
+ };
55
+ export {
56
+ g as default
57
+ };
@@ -14,7 +14,6 @@ const g = [
14
14
  "outlineColor",
15
15
  "borderRadius",
16
16
  // colors
17
- "color",
18
17
  "bgcolor",
19
18
  "backgroundColor",
20
19
  // spacing
@@ -122,11 +121,12 @@ const g = [
122
121
  "typography",
123
122
  // other common system props
124
123
  "spacing"
125
- ], d = ["Box", "Stack", "Typography", "Link"], u = {
126
- Typography: ["color"],
127
- // semantic colors only
128
- Link: ["color"]
129
- }, h = {
124
+ ], c = {
125
+ Container: ["maxWidth"],
126
+ Dialog: ["maxWidth"],
127
+ InputAdornment: ["position"],
128
+ TableCell: ["padding"]
129
+ }, f = {
130
130
  meta: {
131
131
  type: "problem",
132
132
  docs: {
@@ -139,48 +139,41 @@ const g = [
139
139
  },
140
140
  schema: []
141
141
  },
142
- create(n) {
143
- const o = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set();
142
+ create(l) {
143
+ const r = /* @__PURE__ */ new Map(), m = /* @__PURE__ */ new Set();
144
144
  return {
145
- ImportDeclaration(l) {
146
- const r = l;
147
- r.source.value === "@clearstory/drywall-react" && r.specifiers.forEach((e) => {
148
- e.type === "ImportSpecifier" && e.imported && d.includes(e.imported.name) ? o.add(e.local.name) : e.type === "ImportNamespaceSpecifier" && s.add(e.local.name);
145
+ ImportDeclaration(i) {
146
+ const o = i;
147
+ o.source.value === "@clearstory/drywall-react" && o.specifiers.forEach((e) => {
148
+ e.type === "ImportSpecifier" && e.imported ? r.set(e.local.name, e.imported.name) : e.type === "ImportNamespaceSpecifier" && m.add(e.local.name);
149
149
  });
150
150
  },
151
- JSXOpeningElement(l) {
152
- const r = l;
151
+ JSXOpeningElement(i) {
152
+ const o = i;
153
153
  let e;
154
- if ("name" in r.name) {
155
- if (e = r.name.name, !o.has(e))
154
+ if ("name" in o.name) {
155
+ if (e = o.name.name, !r.has(e))
156
156
  return;
157
- } else if (r.name.type === "JSXMemberExpression") {
158
- const t = r.name.object.name;
159
- if (e = r.name.property.name, !s.has(t) || !d.includes(e))
157
+ } else if (o.name.type === "JSXMemberExpression") {
158
+ const t = o.name.object.name;
159
+ if (e = o.name.property.name, !m.has(t))
160
160
  return;
161
161
  } else
162
162
  return;
163
163
  const p = [];
164
- r.attributes.forEach((t) => {
165
- const i = t;
166
- if (i.type !== "JSXAttribute" || !i.name)
164
+ o.attributes.forEach((t) => {
165
+ const n = t;
166
+ if (n.type !== "JSXAttribute" || !n.name)
167
167
  return;
168
- const a = i.name.name;
168
+ const a = n.name.name;
169
169
  if (a === "sx" || !g.includes(a))
170
170
  return;
171
- const m = u[e];
172
- if (m && m.includes(a))
173
- if (a === "color" && i.value) {
174
- const c = f(i.value);
175
- if (y(c))
176
- return;
177
- } else
178
- return;
179
- p.push(
180
- i
171
+ const s = r.get(e) ?? e, d = c[s];
172
+ d && d.includes(a) || p.push(
173
+ n
181
174
  );
182
175
  }), p.forEach((t) => {
183
- n.report({
176
+ l.report({
184
177
  node: t,
185
178
  messageId: "systemProp",
186
179
  data: {
@@ -193,30 +186,6 @@ const g = [
193
186
  };
194
187
  }
195
188
  };
196
- function f(n) {
197
- if (!n) return null;
198
- const o = n;
199
- if (o.type === "Literal")
200
- return String(o.value);
201
- if (o.type === "JSXExpressionContainer" && o.expression) {
202
- if (o.expression.type === "Literal")
203
- return String(o.expression.value);
204
- if (o.expression.type === "Identifier")
205
- return o.expression.name || null;
206
- }
207
- return null;
208
- }
209
- function y(n) {
210
- return [
211
- "inherit",
212
- "primary",
213
- "secondary",
214
- "error",
215
- "warning",
216
- "info",
217
- "success"
218
- ].includes(n || "");
219
- }
220
189
  export {
221
- h as default
190
+ f as default
222
191
  };
@@ -0,0 +1,3 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,72 @@
1
+ import { createSxVisitor as d } from "./sx-utils.js";
2
+ const l = {
3
+ // Positioning (inset)
4
+ top: "insetBlockStart",
5
+ bottom: "insetBlockEnd",
6
+ left: "insetInlineStart",
7
+ right: "insetInlineEnd",
8
+ // Padding
9
+ paddingTop: "paddingBlockStart",
10
+ paddingBottom: "paddingBlockEnd",
11
+ paddingLeft: "paddingInlineStart",
12
+ paddingRight: "paddingInlineEnd",
13
+ paddingX: "paddingInline",
14
+ paddingY: "paddingBlock",
15
+ // Margin
16
+ marginTop: "marginBlockStart",
17
+ marginBottom: "marginBlockEnd",
18
+ marginLeft: "marginInlineStart",
19
+ marginRight: "marginInlineEnd",
20
+ marginX: "marginInline",
21
+ marginY: "marginBlock",
22
+ // Border
23
+ borderTop: "borderBlockStart",
24
+ borderBottom: "borderBlockEnd",
25
+ borderLeft: "borderInlineStart",
26
+ borderRight: "borderInlineEnd",
27
+ // Border color
28
+ borderTopColor: "borderBlockStartColor",
29
+ borderBottomColor: "borderBlockEndColor",
30
+ borderLeftColor: "borderInlineStartColor",
31
+ borderRightColor: "borderInlineEndColor"
32
+ }, g = {
33
+ meta: {
34
+ type: "suggestion",
35
+ fixable: "code",
36
+ docs: {
37
+ description: "Enforce CSS logical properties instead of physical direction properties in sx props",
38
+ category: "Best Practices",
39
+ recommended: !0
40
+ },
41
+ messages: {
42
+ physicalProperty: 'Use logical property "{{replacement}}" instead of "{{property}}" in sx for RTL support.'
43
+ },
44
+ schema: []
45
+ },
46
+ create(t) {
47
+ return d({
48
+ onProperty(n, o) {
49
+ const r = l[n];
50
+ r && t.report({
51
+ node: o,
52
+ messageId: "physicalProperty",
53
+ data: { property: n, replacement: r },
54
+ fix(i) {
55
+ const e = o;
56
+ if (o.type === "Literal") {
57
+ const a = t.sourceCode.getText(e)[0];
58
+ return i.replaceText(
59
+ e,
60
+ `${a}${r}${a}`
61
+ );
62
+ }
63
+ return i.replaceText(e, r);
64
+ }
65
+ });
66
+ }
67
+ });
68
+ }
69
+ };
70
+ export {
71
+ g as default
72
+ };
@@ -1,4 +1,5 @@
1
- const b = /* @__PURE__ */ new Set([
1
+ import { createSxVisitor as l } from "./sx-utils.js";
2
+ const f = /* @__PURE__ */ new Set([
2
3
  "color",
3
4
  "backgroundColor",
4
5
  "bgcolor",
@@ -22,7 +23,7 @@ const b = /* @__PURE__ */ new Set([
22
23
  "initial",
23
24
  "unset",
24
25
  "none"
25
- ]), C = {
26
+ ]), d = {
26
27
  meta: {
27
28
  type: "suggestion",
28
29
  docs: {
@@ -36,155 +37,94 @@ const b = /* @__PURE__ */ new Set([
36
37
  },
37
38
  schema: []
38
39
  },
39
- create(t) {
40
- return {
41
- JSXOpeningElement(e) {
42
- const r = e.attributes.find(
43
- (s) => s.type === "JSXAttribute" && s.name?.name === "sx"
44
- );
45
- if (!r) return;
46
- const o = r.value;
47
- if (o && o.type === "JSXExpressionContainer") {
48
- const s = o.expression;
49
- y(t, s);
50
- }
40
+ create(s) {
41
+ return l({
42
+ onProperty(t, r, e) {
43
+ f.has(t) && i(s, t, e);
44
+ },
45
+ onCallback(t) {
46
+ c(s, t);
51
47
  }
52
- };
48
+ });
53
49
  }
54
50
  };
55
- function y(t, e) {
56
- if (e)
57
- switch (e.type) {
58
- case "ObjectExpression":
59
- u(t, e);
60
- break;
61
- case "ArrowFunctionExpression":
62
- case "FunctionExpression": {
63
- d(t, e);
64
- const n = g(e);
65
- n && u(t, n);
66
- break;
67
- }
68
- case "ArrayExpression": {
69
- e.elements.forEach((r) => {
70
- r && y(t, r);
71
- });
72
- break;
73
- }
74
- }
75
- }
76
- function u(t, e) {
77
- e.properties.forEach((r) => {
78
- if (r.type !== "Property") return;
79
- const o = r, s = m(o.key, o.computed);
80
- if (!s) return;
81
- const i = o.value;
82
- if (i.type === "ObjectExpression") {
83
- u(t, i);
84
- return;
85
- }
86
- if (b.has(s)) {
87
- if (i.type === "ArrowFunctionExpression" || i.type === "FunctionExpression") {
88
- d(t, i);
89
- return;
90
- }
91
- if (i.type === "Literal") {
92
- const a = i.value;
93
- typeof a == "string" && !p.has(a) && t.report({
94
- node: i,
95
- messageId: "stringColor",
96
- data: { value: a, property: s }
97
- });
98
- return;
99
- }
100
- if (i.type === "TemplateLiteral") {
101
- const a = i;
102
- if (a.expressions.length === 0) {
103
- const c = a.quasis[0]?.value.raw;
104
- c && !p.has(c) && t.report({
105
- node: i,
106
- messageId: "stringColor",
107
- data: { value: c, property: s }
108
- });
109
- }
110
- return;
111
- }
112
- if (i.type === "ConditionalExpression") {
113
- const a = i;
114
- l(t, a.consequent, s), l(t, a.alternate, s);
115
- }
51
+ function i(s, t, r) {
52
+ if (r.type === "ObjectExpression") {
53
+ const e = r.properties;
54
+ for (const o of e) {
55
+ if (o.type === "SpreadElement" || o.type !== "Property") continue;
56
+ i(s, t, o.value);
116
57
  }
117
- });
118
- }
119
- function l(t, e, n) {
120
- if (e.type === "Literal") {
121
- const r = e.value;
122
- typeof r == "string" && !p.has(r) && t.report({
123
- node: e,
58
+ return;
59
+ }
60
+ if (r.type === "ArrowFunctionExpression" || r.type === "FunctionExpression") {
61
+ c(s, r);
62
+ return;
63
+ }
64
+ if (r.type === "Literal") {
65
+ const e = r.value;
66
+ typeof e == "string" && !p.has(e) && s.report({
67
+ node: r,
124
68
  messageId: "stringColor",
125
- data: { value: r, property: n }
69
+ data: { value: e, property: t }
126
70
  });
127
- } else if (e.type === "ConditionalExpression") {
128
- const r = e;
129
- l(t, r.consequent, n), l(t, r.alternate, n);
71
+ return;
72
+ }
73
+ if (r.type === "TemplateLiteral") {
74
+ const e = r;
75
+ if (e.expressions.length === 0) {
76
+ const o = e.quasis[0]?.value.raw;
77
+ o && !p.has(o) && s.report({
78
+ node: r,
79
+ messageId: "stringColor",
80
+ data: { value: o, property: t }
81
+ });
82
+ }
83
+ return;
84
+ }
85
+ if (r.type === "ConditionalExpression") {
86
+ const e = r;
87
+ i(s, t, e.consequent), i(s, t, e.alternate);
130
88
  }
131
89
  }
132
- function d(t, e) {
133
- const n = e.params;
134
- if (!n || n.length === 0) return;
135
- const r = n[0];
136
- if (r.type === "Identifier") {
137
- const o = r.name, s = e.body;
138
- f(t, s, o);
139
- } else r.type === "ObjectPattern" && r.properties.forEach((s) => {
140
- if (s.type !== "Property") return;
141
- s.key.name === "palette" && t.report({
142
- node: s,
90
+ function c(s, t) {
91
+ const r = t.params;
92
+ if (!r || r.length === 0) return;
93
+ const e = r[0];
94
+ if (e.type === "Identifier") {
95
+ const o = e.name, n = t.body;
96
+ a(s, n, o);
97
+ } else e.type === "ObjectPattern" && e.properties.forEach((n) => {
98
+ if (n.type !== "Property") return;
99
+ n.key.name === "palette" && s.report({
100
+ node: n,
143
101
  messageId: "useVarsPalette",
144
102
  data: { access: "destructured palette" }
145
103
  });
146
104
  });
147
105
  }
148
- function f(t, e, n) {
149
- if (!(!e || typeof e != "object")) {
150
- if (e.type === "MemberExpression") {
151
- const r = e.object, o = e.property;
152
- if (r.type === "Identifier" && r.name === n && o.name === "palette") {
153
- t.report({
154
- node: e,
106
+ function a(s, t, r) {
107
+ if (!(!t || typeof t != "object")) {
108
+ if (t.type === "MemberExpression") {
109
+ const e = t.object, o = t.property;
110
+ if (e.type === "Identifier" && e.name === r && o.name === "palette") {
111
+ s.report({
112
+ node: t,
155
113
  messageId: "useVarsPalette",
156
- data: { access: `${n}.palette` }
114
+ data: { access: `${r}.palette` }
157
115
  });
158
116
  return;
159
117
  }
160
118
  }
161
- for (const r of Object.keys(e)) {
162
- if (r === "parent") continue;
163
- const o = e[r];
164
- o && typeof o == "object" && (Array.isArray(o) ? o.forEach((s) => {
165
- s && typeof s == "object" && "type" in s && f(t, s, n);
166
- }) : "type" in o && f(t, o, n));
119
+ for (const e of Object.keys(t)) {
120
+ if (e === "parent") continue;
121
+ const o = t[e];
122
+ o && typeof o == "object" && (Array.isArray(o) ? o.forEach((n) => {
123
+ n && typeof n == "object" && "type" in n && a(s, n, r);
124
+ }) : "type" in o && a(s, o, r));
167
125
  }
168
126
  }
169
127
  }
170
- function g(t) {
171
- const e = t.body;
172
- if (e.type === "ObjectExpression")
173
- return e;
174
- if (e.type === "BlockStatement") {
175
- const n = e.body;
176
- for (const r of n)
177
- if (r.type === "ReturnStatement") {
178
- const o = r.argument;
179
- if (o?.type === "ObjectExpression")
180
- return o;
181
- }
182
- }
183
- return null;
184
- }
185
- function m(t, e) {
186
- return t.type === "Identifier" && !e ? t.name || null : t.type === "Literal" && typeof t.value == "string" ? t.value : null;
187
- }
188
128
  export {
189
- C as default
129
+ d as default
190
130
  };
@@ -0,0 +1,53 @@
1
+ import { Rule } from 'eslint';
2
+ /**
3
+ * Shared AST traversal utilities for ESLint rules that inspect MUI `sx` props.
4
+ *
5
+ * Provides a visitor pattern that:
6
+ * 1. Finds the `sx` attribute on JSXOpeningElement nodes
7
+ * 2. Traverses the expression (object, callback, or array)
8
+ * 3. Recurses into nested objects (pseudo-selectors like "&:hover")
9
+ * 4. Calls rule-specific callbacks for each property encountered
10
+ */
11
+ /**
12
+ * Minimal AST node type for traversal within ESLint rules.
13
+ */
14
+ export type ASTNode = Record<string, unknown> & {
15
+ type: string;
16
+ };
17
+ export interface SxVisitorCallbacks {
18
+ /**
19
+ * Called for each property in an sx object expression, including properties
20
+ * inside nested objects (pseudo-selectors like "&:hover").
21
+ * Recursion into nested objects is handled automatically by the visitor.
22
+ */
23
+ onProperty: (keyName: string, keyNode: ASTNode, valueNode: ASTNode) => void;
24
+ /**
25
+ * Called when the sx value (or an array element) is a callback function
26
+ * (ArrowFunctionExpression or FunctionExpression). Fires before the
27
+ * returned object's properties are visited via onProperty.
28
+ */
29
+ onCallback?: (func: ASTNode) => void;
30
+ }
31
+ /**
32
+ * Creates a JSXOpeningElement visitor that locates the `sx` attribute
33
+ * and walks its value, invoking the provided callbacks for each property.
34
+ */
35
+ export declare function createSxVisitor(callbacks: SxVisitorCallbacks): Rule.RuleListener;
36
+ /**
37
+ * Walk an sx value expression (object, callback, or array) and invoke
38
+ * the provided callbacks for each property encountered.
39
+ */
40
+ export declare function visitSxValue(node: ASTNode, callbacks: SxVisitorCallbacks): void;
41
+ /**
42
+ * Extract the returned ObjectExpression from a function/arrow function body.
43
+ */
44
+ export declare function getReturnedObjectExpression(func: ASTNode): ASTNode | null;
45
+ /**
46
+ * Get the string name of a property key.
47
+ * Returns null for computed keys or non-string keys.
48
+ */
49
+ export declare function getKeyName(key: {
50
+ type: string;
51
+ name?: string;
52
+ value?: string;
53
+ }, computed: boolean): string | null;
@@ -0,0 +1,67 @@
1
+ function f(e) {
2
+ return {
3
+ JSXOpeningElement(t) {
4
+ const n = t.attributes.find(
5
+ (i) => i.type === "JSXAttribute" && i.name?.name === "sx"
6
+ );
7
+ if (!n) return;
8
+ const r = n.value;
9
+ if (r && r.type === "JSXExpressionContainer") {
10
+ const i = r.expression;
11
+ u(i, e);
12
+ }
13
+ }
14
+ };
15
+ }
16
+ function u(e, t) {
17
+ if (e)
18
+ switch (e.type) {
19
+ case "ObjectExpression":
20
+ o(e, t);
21
+ break;
22
+ case "ArrowFunctionExpression":
23
+ case "FunctionExpression": {
24
+ t.onCallback?.(e);
25
+ const s = p(e);
26
+ s && o(s, t);
27
+ break;
28
+ }
29
+ case "ArrayExpression": {
30
+ e.elements.forEach((n) => {
31
+ n && u(n, t);
32
+ });
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ function o(e, t) {
38
+ e.properties.forEach((n) => {
39
+ if (n.type !== "Property") return;
40
+ const r = n, i = c(r.key, r.computed);
41
+ i && (r.value.type === "ObjectExpression" && o(r.value, t), t.onProperty(i, r.key, r.value));
42
+ });
43
+ }
44
+ function p(e) {
45
+ const t = e.body;
46
+ if (t.type === "ObjectExpression")
47
+ return t;
48
+ if (t.type === "BlockStatement") {
49
+ const s = t.body;
50
+ for (const n of s)
51
+ if (n.type === "ReturnStatement") {
52
+ const r = n.argument;
53
+ if (r?.type === "ObjectExpression")
54
+ return r;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ function c(e, t) {
60
+ return e.type === "Identifier" && !t ? e.name || null : e.type === "Literal" && typeof e.value == "string" ? e.value : null;
61
+ }
62
+ export {
63
+ f as createSxVisitor,
64
+ c as getKeyName,
65
+ p as getReturnedObjectExpression,
66
+ u as visitSxValue
67
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clearstory/drywall-react",
3
- "version": "7.1.0",
3
+ "version": "7.2.1",
4
4
  "license": "UNLICENSED",
5
5
  "description": "a Clearstory software design system",
6
6
  "type": "module",