This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch chore/lint-cleanup-tech-debt-3
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 6c75540e1c856a8b62a77c83436275d72068a350
Author: Evan Rusackas <[email protected]>
AuthorDate: Wed Feb 11 00:23:31 2026 -0800

    chore(lint): upgrade array creation, effect, and TypeScript rules
    
    - Upgrade unicorn/no-new-array to error (all violations fixed)
    - Upgrade @typescript-eslint/no-use-before-define from warn to error
    - Add react-you-might-not-need-an-effect plugin with rules:
      - no-adjust-state-on-prop-change: error
      - no-pass-data-to-parent: error
    
    Fixed unicorn/no-new-array violations by replacing:
    - new Array(n).fill().map() with Array.from({ length: n }, callback)
    - new Array(n).fill().join() with String.repeat()
    - new Array(n).fill().forEach() with for loops
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 superset-frontend/.eslintrc.js                             |  8 +++++++-
 superset-frontend/oxlint.json                              |  3 ++-
 .../plugins/legacy-preset-chart-deckgl/src/utils.ts        |  2 +-
 .../plugin-chart-echarts/src/Timeseries/transformers.ts    |  9 +++++++--
 .../src/DataTable/components/Pagination.tsx                | 10 +++++-----
 superset-frontend/src/SqlLab/fixtures.ts                   |  2 +-
 .../src/SqlLab/utils/emptyQueryResults.test.ts             |  6 ++----
 .../src/components/FacePile/FacePile.stories.tsx           |  2 +-
 .../src/components/FacePile/FacePile.test.tsx              |  2 +-
 superset-frontend/src/components/FacePile/utils.tsx        |  4 ++--
 .../src/components/ListView/CardCollection.tsx             |  6 +++---
 .../FilterBar/FilterControls/FilterControls.tsx            | 14 +++++++-------
 .../FiltersConfigModal/FilterConfigPane.test.tsx           | 10 ++++------
 .../src/pages/ExecutionLogList/ExecutionLogList.test.tsx   |  2 +-
 superset-frontend/src/pages/Home/index.tsx                 |  2 +-
 superset-frontend/src/pages/RolesList/RolesList.test.tsx   |  6 +++---
 .../src/pages/SavedQueryList/SavedQueryList.test.tsx       |  2 +-
 .../src/pages/UserRegistrations/UserRegistrations.test.tsx |  2 +-
 superset-frontend/src/pages/UsersList/UsersList.test.tsx   |  4 ++--
 19 files changed, 52 insertions(+), 44 deletions(-)

diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js
index 5f4b50ff9d2..2a8cf1529e1 100644
--- a/superset-frontend/.eslintrc.js
+++ b/superset-frontend/.eslintrc.js
@@ -136,6 +136,7 @@ module.exports = {
     'i18n-strings',
     'react-prefer-function-component',
     'prettier',
+    'react-you-might-not-need-an-effect',
   ],
   rules: {
     // === Essential Superset customizations ===
@@ -241,6 +242,11 @@ module.exports = {
     // File progress
     'file-progress/activate': 1,
 
+    // React effect rules
+    'react-you-might-not-need-an-effect/no-adjust-state-on-prop-change':
+      'error',
+    'react-you-might-not-need-an-effect/no-pass-data-to-parent': 'error',
+
     // Restricted imports
     'no-restricted-imports': [
       'error',
@@ -350,7 +356,7 @@ module.exports = {
         ],
         '@typescript-eslint/no-empty-function': 0,
         '@typescript-eslint/no-explicit-any': 0,
-        '@typescript-eslint/no-use-before-define': 1,
+        '@typescript-eslint/no-use-before-define': 'error',
         '@typescript-eslint/no-non-null-assertion': 0,
         '@typescript-eslint/explicit-function-return-type': 0,
         '@typescript-eslint/explicit-module-boundary-types': 0,
diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json
index 81b7523dce5..2fed1be8cb7 100644
--- a/superset-frontend/oxlint.json
+++ b/superset-frontend/oxlint.json
@@ -231,7 +231,7 @@
     "@typescript-eslint/ban-types": "off",
     "@typescript-eslint/no-empty-function": "off",
     "@typescript-eslint/no-explicit-any": "off",
-    "@typescript-eslint/no-use-before-define": "warn",
+    "@typescript-eslint/no-use-before-define": "error",
     "@typescript-eslint/no-non-null-assertion": "off",
     "@typescript-eslint/explicit-function-return-type": "off",
     "@typescript-eslint/explicit-module-boundary-types": "off",
@@ -250,6 +250,7 @@
     ],
 
     // === Unicorn rules (bonus coverage) ===
+    "unicorn/no-new-array": "error",
     "unicorn/filename-case": "off",
     "unicorn/prevent-abbreviations": "off",
     "unicorn/no-null": "off",
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts 
b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts
index 7b6399676e5..6927e6306d7 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts
@@ -84,7 +84,7 @@ export function getBreakPoints(
       delta === 0 ? 0 : Math.max(0, Math.ceil(Math.log10(1 / delta)));
 
     // Generate breakpoints
-    const breakPoints = new Array(numBuckets + 1).fill(0).map((_, i) => {
+    const breakPoints = Array.from({ length: numBuckets + 1 }, (_, i) => {
       const value = minValue + i * delta;
 
       // For the first breakpoint, floor to ensure minimum is included
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
index ba114ceb0dc..dd054d42b50 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
@@ -76,7 +76,10 @@ export const getBaselineSeriesForStream = (
   seriesType: EchartsTimeseriesSeriesType,
 ) => {
   const seriesLength = series[0].length;
-  const baselineSeriesDelta = new Array(seriesLength).fill([0, 0]);
+  const baselineSeriesDelta: [string | number, number][] = Array.from(
+    { length: seriesLength },
+    () => [0, 0],
+  );
   const getVal = (value: number | null) => value ?? 0;
   for (let i = 0; i < seriesLength; i += 1) {
     let seriesSum = 0;
@@ -98,7 +101,9 @@ export const getBaselineSeriesForStream = (
     }
     baselineSeriesDelta[i] = [series[0][i][0], -weightedSeriesSum / seriesSum];
   }
-  const baselineSeries = baselineSeriesDelta.reduce((acc, curr, i) => {
+  const baselineSeries = baselineSeriesDelta.reduce<
+    [string | number, number][]
+  >((acc, curr, i) => {
     if (i === 0) {
       acc.push(curr);
     } else {
diff --git 
a/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx
 
b/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx
index 87040545338..39e599d1285 100644
--- 
a/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx
+++ 
b/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx
@@ -49,16 +49,16 @@ export function generatePageItems(
     throw new Error(`Must allow odd number of page items`);
   }
   if (total < width) {
-    return [...new Array(total).keys()];
+    return Array.from({ length: total }, (_, i) => i);
   }
   const left = Math.max(
     0,
     Math.min(total - width, current - Math.floor(width / 2)),
   );
-  const items: (string | number)[] = new Array(width);
-  for (let i = 0; i < width; i += 1) {
-    items[i] = i + left;
-  }
+  const items: (string | number)[] = Array.from(
+    { length: width },
+    (_, i) => i + left,
+  );
   // replace non-ending items with placeholders
   if (typeof items[0] === 'number' && items[0] > 0) {
     items[0] = 0;
diff --git a/superset-frontend/src/SqlLab/fixtures.ts 
b/superset-frontend/src/SqlLab/fixtures.ts
index e03ecebb162..05bd77b4af7 100644
--- a/superset-frontend/src/SqlLab/fixtures.ts
+++ b/superset-frontend/src/SqlLab/fixtures.ts
@@ -775,7 +775,7 @@ export const testQuery: ISaveableDatasource = {
   ],
 };
 
-export const mockdatasets = new Array(3).fill(undefined).map((_, i) => ({
+export const mockdatasets = Array.from({ length: 3 }, (_, i) => ({
   changed_by_name: 'user',
   kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
   changed_by: 'user',
diff --git a/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts 
b/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts
index 980de6c81b1..944bb135713 100644
--- a/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts
+++ b/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts
@@ -64,11 +64,9 @@ describe('reduxStateToLocalStorageHelper', () => {
       results: {
         data: [
           {
-            jsonValue: `{"str":"${new Array(
+            jsonValue: `{"str":"${'0'.repeat(
               (LOCALSTORAGE_MAX_QUERY_RESULTS_KB / BYTES_PER_CHAR) * 
KB_STORAGE,
-            )
-              .fill(0)
-              .join('')}"}`,
+            )}"}`,
           },
         ],
       },
diff --git a/superset-frontend/src/components/FacePile/FacePile.stories.tsx 
b/superset-frontend/src/components/FacePile/FacePile.stories.tsx
index 3c59fd1a50b..c0f2ed64fd0 100644
--- a/superset-frontend/src/components/FacePile/FacePile.stories.tsx
+++ b/superset-frontend/src/components/FacePile/FacePile.stories.tsx
@@ -49,7 +49,7 @@ const lastNames = [
   'Tzu',
 ];
 
-const users = new Array(10).fill(undefined).map((_, i) => ({
+const users = Array.from({ length: 10 }, (_, i) => ({
   first_name: firstNames[Math.floor(Math.random() * firstNames.length)],
   last_name: lastNames[Math.floor(Math.random() * lastNames.length)],
   id: i,
diff --git a/superset-frontend/src/components/FacePile/FacePile.test.tsx 
b/superset-frontend/src/components/FacePile/FacePile.test.tsx
index 22954d8f43a..e37128d0b0b 100644
--- a/superset-frontend/src/components/FacePile/FacePile.test.tsx
+++ b/superset-frontend/src/components/FacePile/FacePile.test.tsx
@@ -39,7 +39,7 @@ const mockIsFeatureEnabled = isFeatureEnabled as 
jest.MockedFunction<
   typeof isFeatureEnabled
 >;
 
-const users = new Array(10).fill(undefined).map((_, i) => ({
+const users = Array.from({ length: 10 }, (_, i) => ({
   first_name: 'user',
   last_name: `${i}`,
   id: i,
diff --git a/superset-frontend/src/components/FacePile/utils.tsx 
b/superset-frontend/src/components/FacePile/utils.tsx
index 14996a6661d..4df0fa3535e 100644
--- a/superset-frontend/src/components/FacePile/utils.tsx
+++ b/superset-frontend/src/components/FacePile/utils.tsx
@@ -32,9 +32,9 @@ function stringAsciiPRNG(value: string, m: number) {
 
   let random = charCodes[0] % m;
 
-  new Array(len).fill(undefined).forEach(() => {
+  for (let i = 0; i < len; i += 1) {
     random = (a * random + c) % m;
-  });
+  }
 
   return random;
 }
diff --git a/superset-frontend/src/components/ListView/CardCollection.tsx 
b/superset-frontend/src/components/ListView/CardCollection.tsx
index 7fe7d4505e4..8d192037f38 100644
--- a/superset-frontend/src/components/ListView/CardCollection.tsx
+++ b/superset-frontend/src/components/ListView/CardCollection.tsx
@@ -79,9 +79,9 @@ export default function CardCollection({
     <CardContainer showThumbnails={showThumbnails}>
       {loading &&
         rows.length === 0 &&
-        new Array(25)
-          .fill(undefined)
-          .map((e, i) => <div key={i}>{renderCard({ loading })}</div>)}
+        Array.from({ length: 25 }, (_, i) => (
+          <div key={i}>{renderCard({ loading })}</div>
+        ))}
       {rows.length > 0 &&
         rows.map(row => {
           if (!renderCard) return null;
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
index 1dbc0a4c86b..545496aec34 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
@@ -181,13 +181,13 @@ const FilterControls: FC<FilterControlsProps> = ({
     clearAllTriggers,
     onClearAllComplete,
   );
-  const portalNodes = useMemo(() => {
-    const nodes = new Array(filtersWithValues.length);
-    for (let i = 0; i < filtersWithValues.length; i += 1) {
-      nodes[i] = createHtmlPortalNode();
-    }
-    return nodes;
-  }, [filtersWithValues.length]);
+  const portalNodes = useMemo(
+    () =>
+      Array.from({ length: filtersWithValues.length }, () =>
+        createHtmlPortalNode(),
+      ),
+    [filtersWithValues.length],
+  );
 
   const filterIds = new Set(filtersWithValues.map(item => item.id));
 
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx
index 2c5cc0618c3..c701c19dbc4 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx
@@ -112,18 +112,16 @@ test('filter container should scroll to bottom when 
adding items', async () => {
   const state = {
     dashboardInfo: {
       metadata: {
-        native_filter_configuration: new Array(35)
-          .fill(0)
-          .map((_, index) =>
-            buildNativeFilter(`NATIVE_FILTER-${index}`, `filter-${index}`, []),
-          ),
+        native_filter_configuration: Array.from({ length: 35 }, (_, index) =>
+          buildNativeFilter(`NATIVE_FILTER-${index}`, `filter-${index}`, []),
+        ),
       },
     },
     dashboardLayout,
   };
   const props = {
     ...defaultProps,
-    filters: new Array(35).fill(0).map((_, index) => `NATIVE_FILTER-${index}`),
+    filters: Array.from({ length: 35 }, (_, index) => 
`NATIVE_FILTER-${index}`),
   };
 
   defaultRender(state, props);
diff --git 
a/superset-frontend/src/pages/ExecutionLogList/ExecutionLogList.test.tsx 
b/superset-frontend/src/pages/ExecutionLogList/ExecutionLogList.test.tsx
index a3f8145b60c..01bf922f331 100644
--- a/superset-frontend/src/pages/ExecutionLogList/ExecutionLogList.test.tsx
+++ b/superset-frontend/src/pages/ExecutionLogList/ExecutionLogList.test.tsx
@@ -25,7 +25,7 @@ const reportEndpoint = 'glob:*/api/v1/report/*';
 
 fetchMock.delete(executionLogsEndpoint, {});
 
-const mockAnnotations = new Array(3).fill(undefined).map((_, i) => ({
+const mockAnnotations = Array.from({ length: 3 }, (_, i) => ({
   end_dttm: new Date().toISOString,
   error_message: `report ${i} error message`,
   id: i,
diff --git a/superset-frontend/src/pages/Home/index.tsx 
b/superset-frontend/src/pages/Home/index.tsx
index 6b0d31f31f3..42a4b3be15d 100644
--- a/superset-frontend/src/pages/Home/index.tsx
+++ b/superset-frontend/src/pages/Home/index.tsx
@@ -135,7 +135,7 @@ const bootstrapData = getBootstrapData();
 
 export const LoadingCards = ({ cover }: LoadingProps) => (
   <CardContainer showThumbnails={cover} className="loading-cards">
-    {new Array(loadingCardCount).fill(undefined).map((_, index) => (
+    {Array.from({ length: loadingCardCount }, (_, index) => (
       <ListViewCard
         key={index}
         cover={cover ? false : <></>}
diff --git a/superset-frontend/src/pages/RolesList/RolesList.test.tsx 
b/superset-frontend/src/pages/RolesList/RolesList.test.tsx
index 056491fbadd..23a953dbde5 100644
--- a/superset-frontend/src/pages/RolesList/RolesList.test.tsx
+++ b/superset-frontend/src/pages/RolesList/RolesList.test.tsx
@@ -40,20 +40,20 @@ const roleEndpoint = 'glob:*/api/v1/security/roles/*';
 const permissionsEndpoint = 'glob:*/api/v1/security/permissions-resources/?*';
 const usersEndpoint = 'glob:*/api/v1/security/users/?*';
 
-const mockRoles = new Array(3).fill(undefined).map((_, i) => ({
+const mockRoles = Array.from({ length: 3 }, (_, i) => ({
   id: i,
   name: `role ${i}`,
   user_ids: [i, i + 1],
   permission_ids: [i, i + 1, i + 2],
 }));
 
-const mockPermissions = new Array(10).fill(undefined).map((_, i) => ({
+const mockPermissions = Array.from({ length: 10 }, (_, i) => ({
   id: i,
   permission: { name: `permission_${i}` },
   view_menu: { name: `view_menu_${i}` },
 }));
 
-const mockUsers = new Array(5).fill(undefined).map((_, i) => ({
+const mockUsers = Array.from({ length: 5 }, (_, i) => ({
   id: i,
   username: `user_${i}`,
   first_name: `User`,
diff --git a/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.tsx 
b/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.tsx
index 9758154b292..12cf7e6aba7 100644
--- a/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.tsx
+++ b/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.tsx
@@ -33,7 +33,7 @@ import SavedQueryList from '.';
 // Increase default timeout
 jest.setTimeout(30000);
 
-const mockQueries = new Array(3).fill(undefined).map((_, i) => ({
+const mockQueries = Array.from({ length: 3 }, (_, i) => ({
   created_by: { id: i, first_name: 'user', last_name: `${i}` },
   created_on: `${i}-2020`,
   database: { database_name: `db ${i}`, id: i },
diff --git 
a/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx 
b/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx
index fbdaa57aefc..67856b35661 100644
--- a/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx
+++ b/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx
@@ -23,7 +23,7 @@ import UserRegistrations from '.';
 
 const userRegistrationsEndpoint = 'glob:*/security/user_registrations/?*';
 
-const mockUserRegistrations = new Array(5).fill(undefined).map((_, i) => ({
+const mockUserRegistrations = Array.from({ length: 5 }, (_, i) => ({
   id: i,
   username: `user${i}`,
   first_name: `User${i}`,
diff --git a/superset-frontend/src/pages/UsersList/UsersList.test.tsx 
b/superset-frontend/src/pages/UsersList/UsersList.test.tsx
index 465a0c531fc..e994bd485cc 100644
--- a/superset-frontend/src/pages/UsersList/UsersList.test.tsx
+++ b/superset-frontend/src/pages/UsersList/UsersList.test.tsx
@@ -39,14 +39,14 @@ const rolesEndpoint = 'glob:*/security/roles/?*';
 const usersEndpoint = 'glob:*/security/users/?*';
 const groupsEndpoint = 'glob:*/security/groups/*';
 
-const mockRoles = new Array(3).fill(undefined).map((_, i) => ({
+const mockRoles = Array.from({ length: 3 }, (_, i) => ({
   id: i,
   name: `role ${i}`,
   user_ids: [i, i + 1],
   permission_ids: [i, i + 1, i + 2],
 }));
 
-const mockUsers = new Array(5).fill(undefined).map((_, i) => ({
+const mockUsers = Array.from({ length: 5 }, (_, i) => ({
   active: true,
   changed_by: { id: 1 },
   changed_on: new Date(2025, 2, 25, 11, 4, 32 + i).toISOString(),

Reply via email to