Skip to content

Commit 2299d01

Browse files
committed
fix: resolve bound numbered-label styles
1 parent 94bbd23 commit 2299d01

3 files changed

Lines changed: 31 additions & 11 deletions

File tree

packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-numbered-section-markers.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ describe("no-numbered-section-markers", () => {
3131
expect(result.diagnostics).toHaveLength(1);
3232
});
3333

34+
it("resolves const-bound inline label styles", () => {
35+
const result = runRule(
36+
noNumberedSectionMarkers,
37+
`const labelStyle = { fontFamily: "monospace" };
38+
const Page = () => <main>
39+
<section><span className="text-xs" style={labelStyle}>01</span><h2>Principles</h2></section>
40+
<section><span className="text-xs" style={labelStyle}>02</span><h2>Process</h2></section>
41+
</main>;`,
42+
);
43+
expect(result.diagnostics).toHaveLength(1);
44+
});
45+
3446
it("does not require consecutive indices", () => {
3547
const result = runRule(
3648
noNumberedSectionMarkers,

packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-numbered-section-markers.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
NUMBERED_SECTION_LABEL_MIN_FONT_WEIGHT,
66
NUMBERED_SECTION_LABEL_MIN_COUNT,
77
} from "../../constants/design.js";
8+
import type { ScopeAnalysis } from "../../semantic/scope-analysis.js";
89
import { defineRule } from "../../utils/define-rule.js";
910
import type { EsTreeNode } from "../../utils/es-tree-node.js";
1011
import { getNextStaticJsxElementSibling } from "../../utils/get-next-static-jsx-element-sibling.js";
@@ -158,10 +159,11 @@ const hasUnresolvedJsxAttribute = (
158159

159160
const hasUnresolvedInlineRenderingStyle = (
160161
openingElement: EsTreeNodeOfType<"JSXOpeningElement">,
162+
scopes: ScopeAnalysis,
161163
): boolean => {
162164
const styleAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "style");
163165
if (!styleAttribute) return false;
164-
const styleExpression = getInlineStyleExpression(styleAttribute);
166+
const styleExpression = getInlineStyleExpression(styleAttribute, scopes);
165167
if (!styleExpression) return true;
166168
if (styleExpression.properties.some((property) => getStylePropertyKey(property) === null)) {
167169
return true;
@@ -176,13 +178,14 @@ const hasUnresolvedInlineRenderingStyle = (
176178
const isHiddenOrRenderingUnknown = (
177179
openingElement: EsTreeNodeOfType<"JSXOpeningElement">,
178180
hasTailwind: boolean,
181+
scopes: ScopeAnalysis,
179182
settings: Readonly<Record<string, unknown>> | undefined,
180183
): boolean => {
181184
if (
182185
hasJsxSpreadAttribute(openingElement.attributes) ||
183186
hasUnresolvedJsxAttribute(openingElement, "hidden") ||
184187
hasUnresolvedJsxAttribute(openingElement, "aria-hidden") ||
185-
hasUnresolvedInlineRenderingStyle(openingElement)
188+
hasUnresolvedInlineRenderingStyle(openingElement, scopes)
186189
) {
187190
return true;
188191
}
@@ -191,7 +194,7 @@ const isHiddenOrRenderingUnknown = (
191194
if (classNameAttribute && classNameValue === null) return true;
192195
if (isHiddenFromScreenReader(openingElement, settings)) return true;
193196
const styleAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "style");
194-
const styleExpression = styleAttribute ? getInlineStyleExpression(styleAttribute) : null;
197+
const styleExpression = styleAttribute ? getInlineStyleExpression(styleAttribute, scopes) : null;
195198
if (styleExpression) {
196199
const displayProperty = getEffectiveStyleProperty(styleExpression.properties, "display");
197200
if (displayProperty && getStylePropertyStringValue(displayProperty)?.toLowerCase() === "none") {
@@ -213,10 +216,11 @@ const isHiddenOrRenderingUnknown = (
213216
const hasHiddenOrUnknownAncestor = (
214217
node: EsTreeNodeOfType<"JSXElement">,
215218
hasTailwind: boolean,
219+
scopes: ScopeAnalysis,
216220
settings: Readonly<Record<string, unknown>> | undefined,
217221
): boolean =>
218222
getAncestorOpeningElements(node).some((openingElement) =>
219-
isHiddenOrRenderingUnknown(openingElement, hasTailwind, settings),
223+
isHiddenOrRenderingUnknown(openingElement, hasTailwind, scopes, settings),
220224
);
221225

222226
const parseNumberedSectionLabel = (text: string): number | null => {
@@ -322,10 +326,11 @@ const hasOrderedOrUnresolvedContext = (node: EsTreeNodeOfType<"JSXElement">): bo
322326

323327
const hasInlineMicroLabelTreatment = (
324328
openingElement: EsTreeNodeOfType<"JSXOpeningElement">,
329+
scopes: ScopeAnalysis,
325330
): boolean => {
326331
const styleAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "style");
327332
if (!styleAttribute) return false;
328-
const styleExpression = getInlineStyleExpression(styleAttribute);
333+
const styleExpression = getInlineStyleExpression(styleAttribute, scopes);
329334
if (!styleExpression) return false;
330335
const fontFamily = getEffectiveStyleProperty(styleExpression.properties, "fontFamily");
331336
if (fontFamily && /mono/i.test(getStylePropertyStringValue(fontFamily) ?? "")) return true;
@@ -370,6 +375,7 @@ const hasTailwindMicroLabelTreatment = (
370375
const getSectionMarker = (
371376
node: EsTreeNodeOfType<"JSXElement">,
372377
hasTailwind: boolean,
378+
scopes: ScopeAnalysis,
373379
settings: Readonly<Record<string, unknown>> | undefined,
374380
): NumberedSectionLabel | null => {
375381
const heading = getFollowingStaticHeading(node);
@@ -380,21 +386,21 @@ const getSectionMarker = (
380386
hasConditionalOrLogicalAncestor(node) ||
381387
hasOrderedOrUnresolvedContext(node) ||
382388
hasOrderedOrUnresolvedContext(heading) ||
383-
hasHiddenOrUnknownAncestor(node, hasTailwind, settings) ||
384-
hasHiddenOrUnknownAncestor(heading, hasTailwind, settings)
389+
hasHiddenOrUnknownAncestor(node, hasTailwind, scopes, settings) ||
390+
hasHiddenOrUnknownAncestor(heading, hasTailwind, scopes, settings)
385391
) {
386392
return null;
387393
}
388394
const text = getFullyStaticJsxText(node);
389395
if (text === null) return null;
390396
const index = parseNumberedSectionLabel(text);
391397
if (index === null) return null;
392-
const fontSize = getStaticEffectiveFontSize(node.openingElement, hasTailwind);
398+
const fontSize = getStaticEffectiveFontSize(node.openingElement, hasTailwind, scopes);
393399
if (fontSize === null || fontSize <= 0 || fontSize > NUMBERED_SECTION_LABEL_MAX_FONT_SIZE_PX) {
394400
return null;
395401
}
396402
if (
397-
!hasInlineMicroLabelTreatment(node.openingElement) &&
403+
!hasInlineMicroLabelTreatment(node.openingElement, scopes) &&
398404
!(hasTailwind && hasTailwindMicroLabelTreatment(node.openingElement))
399405
) {
400406
return null;
@@ -418,7 +424,7 @@ export const noNumberedSectionMarkers = defineRule({
418424
>();
419425
return {
420426
JSXElement(node: EsTreeNodeOfType<"JSXElement">) {
421-
const marker = getSectionMarker(node, hasTailwind, context.settings);
427+
const marker = getSectionMarker(node, hasTailwind, context.scopes, context.settings);
422428
if (!marker) return;
423429
const outermostJsxRoot = getOutermostJsxRoot(node);
424430
const existingBucket = markerBuckets.get(outermostJsxRoot);

packages/oxlint-plugin-react-doctor/src/plugin/rules/design/utils/get-static-effective-font-size.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ROOT_FONT_SIZE_PX } from "../../../constants/design.js";
2+
import type { ScopeAnalysis } from "../../../semantic/scope-analysis.js";
23
import type { EsTreeNode } from "../../../utils/es-tree-node.js";
34
import type { EsTreeNodeOfType } from "../../../utils/es-tree-node-of-type.js";
45
import { getAuthoritativeJsxAttribute } from "../../../utils/get-authoritative-jsx-attribute.js";
@@ -32,6 +33,7 @@ const getFontSizePx = (property: EsTreeNode): number | null => {
3233
export const getStaticEffectiveFontSize = (
3334
openingElement: EsTreeNodeOfType<"JSXOpeningElement">,
3435
hasTailwind: boolean,
36+
scopes?: ScopeAnalysis,
3537
): number | null => {
3638
const classNameValue = getStringFromClassNameAttr(openingElement);
3739
const tailwindFontSize = hasTailwind ? getStaticTailwindFontSize(classNameValue) : null;
@@ -43,7 +45,7 @@ export const getStaticEffectiveFontSize = (
4345
if (!styleAttribute) {
4446
return hasJsxSpreadAttribute(openingElement.attributes) ? null : tailwindFontSize;
4547
}
46-
const styleExpression = getInlineStyleExpression(styleAttribute);
48+
const styleExpression = getInlineStyleExpression(styleAttribute, scopes);
4749
if (!styleExpression) return null;
4850
const fontSizeProperty = getEffectiveStyleProperty(styleExpression.properties, "fontSize");
4951
if (fontSizeProperty) return getFontSizePx(fontSizeProperty);

0 commit comments

Comments
 (0)