Skip to content

chore(lint): migrate repo to oxlint and stabilize validation#4075

Open
Saadnajmi wants to merge 7 commits intomicrosoft:mainfrom
Saadnajmi:chore/oxlint-migration-review
Open

chore(lint): migrate repo to oxlint and stabilize validation#4075
Saadnajmi wants to merge 7 commits intomicrosoft:mainfrom
Saadnajmi:chore/oxlint-migration-review

Conversation

@Saadnajmi
Copy link
Copy Markdown
Collaborator

@Saadnajmi Saadnajmi commented Mar 26, 2026

Summary

This PR replaces the repo ESLint-based lint workflow with oxlint, fixes the violations surfaced by that migration, and closes the validation issue that was still breaking the full Lage run.

What changed

1. Migrate the repo from ESLint to oxlint

  • replace package-level eslint.config.js usage with oxlint config entrypoints
  • rename the shared config package to @fluentui-react-native/lint-config-rules
  • update workspace package manifests to use fluentui-scripts lint
  • switch the shared scripts task from eslint to lint
  • add targeted local oxlint overrides where packages need exceptions

2. Fix oxlint violations across the repo

  • replace broad export-all re-exports with explicit exports where required
  • tighten hook dependency lists and callback captures
  • replace ts-ignore with ts-expect-error where the suppression is intentional
  • remove forEach patterns that capture mutable variables
  • fix smaller rule violations across components, deprecated packages, theming, framework, and experimental packages

3. Stabilize full repo validation

  • update Fluent Tester bundling to run platform bundles sequentially
  • rename the root bundle convenience script to bundle:repo
  • avoid the recursive root bundle task that caused the full Lage validation run to exhaust heap
  • align repo docs with the actual supported root commands

Why this is split this way

The commit history is organized to make review easier:

  • chore(lint): migrate repo from eslint to oxlint
  • fix(lint): resolve oxlint violations across packages
  • fix(build): stabilize repo bundle validation

This lets the tooling migration, rule-compliance changes, and validation fix be reviewed independently.

Validation

  • yarn lage build ci-test lint bundle depcheck check-publishing

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 26, 2026

🦋 Changeset detected

Latest commit: 9fad281

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 74 packages
Name Type
@uifabricshared/foundation-composable Patch
@fluentui-react-native/experimental-appearance-additions Patch
@uifabricshared/theming-react-native Patch
@uifabricshared/foundation-settings Patch
@fluentui-react-native/experimental-activity-indicator Patch
@fluentui-react-native/experimental-native-font-metrics Patch
@uifabricshared/foundation-compose Patch
@fluentui-react-native/experimental-native-date-picker Patch
@uifabricshared/foundation-tokens Patch
@fluentui-react-native/themed-stylesheet Patch
@uifabricshared/themed-settings Patch
@fluentui-react-native/contextual-menu Patch
@fluentui-react-native/lint-config-rules Patch
@uifabricshared/theme-registry Patch
@fluentui-react-native/vibrancy-view Patch
@fluentui-react-native/focus-trap-zone Patch
@fluentui-react-native/notification Patch
@uifabricshared/theming-ramp Patch
@fluentui-react-native/experimental-menu-button Patch
@fluentui-react-native/interactive-hooks Patch
@fluentui-react-native/persona-coin Patch
@fluentui-react-native/menu-button Patch
@fluentui-react-native/radio-group Patch
@fluentui-react-native/experimental-checkbox Patch
@fluentui-react-native/dropdown Patch
@fluentui-react-native/experimental-expander Patch
@fluentui-react-native/overflow Patch
@fluentui-react-native/composition Patch
@fluentui-react-native/use-styling Patch
@fluentui-react-native/android-theme Patch
@fluentui-react-native/default-theme Patch
@fluentui-react-native/theming-utils Patch
@fluentui-react-native/focus-zone Patch
@fluentui-react-native/pressable Patch
@fluentui-react-native/separator Patch
@fluentui-react-native/popover Patch
@fluentui-react-native/experimental-shimmer Patch
@fluentui-react-native/spinner Patch
@fluentui-react-native/tooltip Patch
@fluentui-react-native/use-tokens Patch
@fluentui-react-native/theme-tokens Patch
@fluentui-react-native/checkbox Patch
@fluentui-react-native/experimental-avatar Patch
@fluentui-react-native/drawer Patch
@fluentui-react-native/experimental-shadow Patch
@fluentui-react-native/framework Patch
@fluentui-react-native/use-slots Patch
@fluentui-react-native/apple-theme Patch
@fluentui-react-native/theme-types Patch
@fluentui-react-native/win32-theme Patch
@fluentui-react-native/callout Patch
@fluentui-react-native/divider Patch
@fluentui-react-native/persona Patch
@fluentui-react-native/tablist Patch
@fluentui-react-native/use-slot Patch
@fluentui-react-native/avatar Patch
@fluentui-react-native/button Patch
@fluentui-react-native/switch Patch
@fluentui-react-native/badge Patch
@fluentui-react-native/input Patch
@fluentui-react-native/stack Patch
@fluentui-react-native/chip Patch
@fluentui-react-native/icon Patch
@fluentui-react-native/link Patch
@fluentui-react-native/menu Patch
@fluentui-react-native/text Patch
@fluentui-react-native/theme Patch
@fluentui-react-native/framework-base Patch
@fluentui/react-native Patch
@fluentui-react-native/adapters Patch
@fluentui-react-native/styling-utils Patch
@fluentui-react-native/tokens Patch
@fluentui-react-native/tester Patch
@fluentui-react-native/codemods Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Saadnajmi Saadnajmi force-pushed the chore/oxlint-migration-review branch from ba05514 to 7ce82c8 Compare March 26, 2026 00:24
@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

@Saadnajmi Saadnajmi force-pushed the chore/oxlint-migration-review branch from a89f2a3 to 9c3eaf4 Compare April 16, 2026 01:04
@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

Saadnajmi and others added 6 commits April 16, 2026 05:06
The TS build failed because private.ts imports from @rnx-kit/oxlint-config/*
which ships no type declarations. Add a triple-slash reference so the
existing types.d.ts is always in scope, even when other packages compile
this file with their own tsconfig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Saadnajmi Saadnajmi force-pushed the chore/oxlint-migration-review branch 5 times, most recently from b0aac5e to b382af0 Compare April 16, 2026 19:10
Two CI fixes:

1. types.d.ts: Use OxlintConfig type instead of unknown for the
   @rnx-kit/oxlint-config ambient module declarations, since the
   extends field in defineConfig expects OxlintConfig[].

2. depcheck.ts: Fix injectedDevDeps() to read Object.keys from
   the nested .dependencies object rather than the wrapper, so
   dynamically injected deps like oxlint are properly ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Saadnajmi Saadnajmi force-pushed the chore/oxlint-migration-review branch from b382af0 to 0413432 Compare April 16, 2026 19:15
userCallback && userCallback(!isChecked);
setChecked(!isChecked);
}, [isChecked, setChecked]);
}, [isChecked, setChecked, userCallback]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, changes like this (And in useAsPressable) could change behavior

Comment on lines 26 to 32
setHoverState({ hovered: true });
if (props.onHoverIn) {
props.onHoverIn(e);
if (onHoverInProp) {
onHoverInProp(e);
}
},
[setHoverState, props.onHoverIn],
[onHoverInProp, setHoverState],
);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better code but could change behavior

Comment on lines 69 to 74
const slotProps = {};
Object.keys(styles).forEach((key) => {
for (const key of Object.keys(styles)) {
const style = styles[key];
slotProps[key] = typeof style === 'function' ? style(tokens, theme, cache(null, [key])[1]) : style;
});
}
return slotProps as TSlotProps;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting

Comment on lines -132 to +134
Object.keys(slotProps).forEach((key) => {
for (const key of Object.keys(slotProps)) {
expect(slotProps[key]).toBe(slotProps2[key]);
});
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same change here

Comment on lines 59 to 61
}),
[component, filter, slotData],
[filter, slotData],
);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe behavioral change

Comment on lines 41 to 43
const hideOpacity = status === 'inactive' && hidesWhenStopped == true ? 0 : 1;
const rotationAngle = new Animated.Value(0);
const rotationAngle = useRef(new Animated.Value(0)).current;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to check this

Comment on lines 61 to 67
).start();
}, [memoizedShimmerData.delay, memoizedShimmerData.duration]);
}, [endValue, memoizedShimmerData.delay, memoizedShimmerData.duration, x1]);

useEffect(() => {
shimmerAnimation();
});
}, [shimmerAnimation]);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably check this

Comment on lines 65 to 76
// This effect should only run when the size of the item or container changes.
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally only re-run when size or containerSize changes
}, [size, containerSize]);

const onLayout = React.useCallback(
(e: LayoutChangeEvent) => {
const itemSize = { width: e.nativeEvent.layout.width, height: e.nativeEvent.layout.height };
setSize(itemSize);
props.onLayout && props.onLayout(e);
onLayoutProp && onLayoutProp(e);
},
[props],
[onLayoutProp],
); // Get item dimensions
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another one

Comment thread apps/E2E/oxlint.config.ts
Comment on lines +7 to +8
'@rnx-kit/no-export-all': 'off',
'typescript/class-literal-property-style': 'off',
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two seem added

bail: 1, // If you only want to run your tests until a specific amount of tests have failed use bail (default is 0 - don't bail, run all tests).
waitforTimeout: defaultWaitForTimeout, // Default timeout for all waitForXXX commands.
connectionRetryTimeout: defaultConnectionRetryTimeout, // Timeout for any WebDriver request to a driver or grid.
connectionRetryCount: 2, // Maximum count of request retries to the Selenium server.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this?

"build-cjs": "tsgo --outDir lib-commonjs",
"build-core": "tsgo --outDir lib --module esnext --moduleResolution bundler",
"bundle": "rnx-cli bundle --dev false",
"bundle": "yarn bundle:android && yarn bundle:ios && yarn bundle:macos",
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Comment on lines +6 to +11
rules: {
'@rnx-kit/no-export-all': 'off',
'@rnx-kit/no-foreach-with-captured-variables': 'off',
'react-hooks/exhaustive-deps': 'off',
'typescript/array-type': 'off',
},
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add these? Can they be in the base?

}
},
[checkedValue, showCloseIcon, closeIconOnPress],
[closeIconOnPress, closeIconVisibile],
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a change

Comment on lines 59 to +62
const dismissCallback = React.useCallback(() => {
userProps.onDismiss();
onDismiss();
setShowMenu?.(false);
}, [setShowMenu, userProps.onDismiss]);
}, [onDismiss, setShowMenu]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a change

Comment on lines -162 to 164
const transforms = [];
const transforms = useMemo(() => {
const nextTransforms = [];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lot of changes here

Comment on lines 32 to +38
React.useEffect(() => {
listContext.addRadioItem(name);
addRadioItem(name);

return () => {
listContext.removeRadioItem(name);
removeRadioItem(name);
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally run only on mount/unmount
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify this

Comment on lines -37 to +43
const doNotTakePointerCapture = props.doNotTakePointerCapture ?? openOnHover;
const doNotTakePointerCapture = doNotTakePointerCaptureProp ?? openOnHover;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one

}
},
[...refs],
[refs],
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this

Comment on lines -80 to 81
menuItems.forEach((item) => {
for (const item of menuItems) {
const imageSource = extractResolvedImageSourceFromIcon(item.icon);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Comment on lines -27 to +34
const onPressRerouted = () => {
const onPressRerouted = React.useCallback(() => {
// Prevent calls to RadioGroup's onChange on the currently selected button
if (buttonKey != info.selectedKey) {
info.onChange && info.onChange(buttonKey);
info.updateSelectedButtonRef && componentRef && info.updateSelectedButtonRef(componentRef);
if (buttonKey != selectedKey) {
onChange && onChange(buttonKey);
updateSelectedButtonRef && componentRef && updateSelectedButtonRef(componentRef);
}
};
}, [buttonKey, componentRef, onChange, selectedKey, updateSelectedButtonRef]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of changes here

Comment on lines -45 to 50
const changeSelection = () => {
if (buttonKey != info.selectedKey) {
info.onChange && info.onChange(buttonKey);
info.updateSelectedButtonRef && componentRef && info.updateSelectedButtonRef(componentRef);
const changeSelection = React.useCallback(() => {
if (buttonKey != selectedKey) {
onChange && onChange(buttonKey);
updateSelectedButtonRef && componentRef && updateSelectedButtonRef(componentRef);
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here

Comment on lines -67 to 68
buttonKeys.push(buttonKey);
setButtonKeys(buttonKeys);
setButtonKeys((prevButtonKeys) => [...prevButtonKeys, buttonKey]);
},
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

Comment on lines 124 to 126
},
[radioGroupContext],
[enabledValues, isRTL, onChange, selectedValue, updateInvoked],
);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

Comment on lines +13 to +19
".": {
"default": "./private.ts"
},
"./private": {
"default": "./private.ts"
},
"./package.json": "./package.json"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

const resolvedSlotProps = { tokens: tokenPropInfo.tokens || {} };

Object.getOwnPropertyNames(handlers).forEach((slotName) => {
for (const slotName of Object.getOwnPropertyNames(handlers)) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another

});
}
}, [rotationAngle, animating]);
}, [rotationAngle]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

stopRotation();
}
}, [animating, hidesWhenStopped, rotationAngle]);
}, [animating, rotationAngle, startRotation]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

Comment on lines -52 to +63
[props?.onScrimClick],
[onScrimClickProp],
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant