.eslintrc.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. const packageConfig = require('./package.json');
  20. const importCoreModules = [];
  21. Object.entries(packageConfig.dependencies).forEach(([pkg]) => {
  22. if (/@superset-ui/.test(pkg)) {
  23. importCoreModules.push(pkg);
  24. }
  25. });
  26. // ignore files in production mode
  27. let ignorePatterns = [];
  28. if (process.env.NODE_ENV === 'production') {
  29. ignorePatterns = [
  30. '*.test.{js,ts,jsx,tsx}',
  31. 'plugins/**/test/**/*',
  32. 'packages/**/test/**/*',
  33. 'packages/generator-superset/**/*',
  34. ];
  35. }
  36. const restrictedImportsRules = {
  37. 'no-design-icons': {
  38. name: '@ant-design/icons',
  39. message:
  40. 'Avoid importing icons directly from @ant-design/icons. Use the src/components/Icons component instead.',
  41. },
  42. 'no-moment': {
  43. name: 'moment',
  44. message:
  45. 'Please use the dayjs library instead of moment.js. See https://day.js.org',
  46. },
  47. 'no-lodash-memoize': {
  48. name: 'lodash/memoize',
  49. message: 'Lodash Memoize is unsafe! Please use memoize-one instead',
  50. },
  51. 'no-testing-library-react': {
  52. name: '@superset-ui/core/spec',
  53. message: 'Please use spec/helpers/testing-library instead',
  54. },
  55. 'no-testing-library-react-dom-utils': {
  56. name: '@testing-library/react-dom-utils',
  57. message: 'Please use spec/helpers/testing-library instead',
  58. },
  59. 'no-antd': {
  60. name: 'antd',
  61. message: 'Please import Ant components from the index of src/components',
  62. },
  63. 'no-superset-theme': {
  64. name: '@superset-ui/core',
  65. importNames: ['supersetTheme'],
  66. message:
  67. 'Please use the theme directly from the ThemeProvider rather than importing supersetTheme.',
  68. },
  69. 'no-query-string': {
  70. name: 'query-string',
  71. message: 'Please use the URLSearchParams API instead of query-string.',
  72. },
  73. };
  74. module.exports = {
  75. extends: [
  76. 'eslint:recommended',
  77. 'plugin:import/recommended',
  78. 'plugin:react/recommended',
  79. 'plugin:jsx-a11y/recommended',
  80. 'plugin:react-hooks/recommended',
  81. 'plugin:react-prefer-function-component/recommended',
  82. 'plugin:storybook/recommended',
  83. 'prettier',
  84. ],
  85. parser: '@babel/eslint-parser',
  86. parserOptions: {
  87. ecmaVersion: 2020,
  88. sourceType: 'module',
  89. ecmaFeatures: {
  90. jsx: true,
  91. },
  92. requireConfigFile: false,
  93. babelOptions: {
  94. presets: ['@babel/preset-react', '@babel/preset-env'],
  95. },
  96. },
  97. env: {
  98. browser: true,
  99. node: true,
  100. es2020: true,
  101. },
  102. settings: {
  103. 'import/resolver': {
  104. node: {
  105. extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
  106. moduleDirectory: ['node_modules', '.'],
  107. },
  108. typescript: {
  109. alwaysTryTypes: true,
  110. project: [
  111. './tsconfig.json',
  112. './packages/superset-ui-core/tsconfig.json',
  113. './packages/superset-ui-chart-controls/',
  114. './plugins/*/tsconfig.json',
  115. ],
  116. },
  117. },
  118. 'import/core-modules': importCoreModules,
  119. react: {
  120. version: 'detect',
  121. },
  122. },
  123. plugins: [
  124. 'import',
  125. 'react',
  126. 'jsx-a11y',
  127. 'react-hooks',
  128. 'file-progress',
  129. 'lodash',
  130. 'theme-colors',
  131. 'icons',
  132. 'i18n-strings',
  133. 'react-prefer-function-component',
  134. 'prettier',
  135. ],
  136. rules: {
  137. // === Essential Superset customizations ===
  138. // Prettier integration
  139. 'prettier/prettier': 'error',
  140. // Custom Superset rules
  141. 'theme-colors/no-literal-colors': 'error',
  142. 'icons/no-fa-icons-usage': 'error',
  143. 'i18n-strings/no-template-vars': ['error', true],
  144. // Core ESLint overrides for Superset
  145. 'no-console': 'warn',
  146. 'no-unused-vars': 'off', // TypeScript handles this
  147. camelcase: [
  148. 'error',
  149. {
  150. allow: ['^UNSAFE_', '__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'],
  151. properties: 'never',
  152. },
  153. ],
  154. 'prefer-destructuring': ['error', { object: true, array: false }],
  155. 'no-prototype-builtins': 0,
  156. curly: 'off',
  157. // Import plugin overrides
  158. 'import/extensions': [
  159. 'error',
  160. 'ignorePackages',
  161. {
  162. js: 'never',
  163. jsx: 'never',
  164. ts: 'never',
  165. tsx: 'never',
  166. },
  167. ],
  168. 'import/no-cycle': 0,
  169. 'import/prefer-default-export': 0,
  170. 'import/no-named-as-default-member': 0,
  171. 'import/no-extraneous-dependencies': [
  172. 'error',
  173. {
  174. devDependencies: [
  175. 'test/**',
  176. 'tests/**',
  177. 'spec/**',
  178. '**/__tests__/**',
  179. '**/__mocks__/**',
  180. '*.test.{js,jsx,ts,tsx}',
  181. '*.spec.{js,jsx,ts,tsx}',
  182. '**/*.test.{js,jsx,ts,tsx}',
  183. '**/*.spec.{js,jsx,ts,tsx}',
  184. '**/jest.config.js',
  185. '**/jest.setup.js',
  186. '**/webpack.config.js',
  187. '**/webpack.config.*.js',
  188. '**/.eslintrc.js',
  189. ],
  190. optionalDependencies: false,
  191. },
  192. ],
  193. // React plugin overrides
  194. 'react/prop-types': 0,
  195. 'react/require-default-props': 0,
  196. 'react/forbid-prop-types': 0,
  197. 'react/forbid-component-props': 1,
  198. 'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
  199. 'react/jsx-fragments': 1,
  200. 'react/jsx-no-bind': 0,
  201. 'react/jsx-props-no-spreading': 0,
  202. 'react/no-array-index-key': 0,
  203. 'react/no-string-refs': 0,
  204. 'react/no-unescaped-entities': 0,
  205. 'react/no-unused-prop-types': 0,
  206. 'react/destructuring-assignment': 0,
  207. 'react/sort-comp': 0,
  208. 'react/static-property-placement': 0,
  209. 'react-prefer-function-component/react-prefer-function-component': 1,
  210. 'react/react-in-jsx-scope': 0,
  211. 'react/no-unknown-property': 0,
  212. 'react/no-void-elements': 0,
  213. 'react/function-component-definition': [
  214. 0,
  215. {
  216. namedComponents: 'arrow-function',
  217. },
  218. ],
  219. 'react/no-unstable-nested-components': 0,
  220. 'react/jsx-no-useless-fragment': 0,
  221. 'react/no-unused-class-component-methods': 0,
  222. // JSX-a11y overrides
  223. 'jsx-a11y/anchor-is-valid': 1,
  224. 'jsx-a11y/click-events-have-key-events': 0,
  225. 'jsx-a11y/mouse-events-have-key-events': 0,
  226. 'jsx-a11y/no-static-element-interactions': 0,
  227. // Lodash
  228. 'lodash/import-scope': [2, 'member'],
  229. // File progress
  230. 'file-progress/activate': 1,
  231. // Restricted imports
  232. 'no-restricted-imports': [
  233. 'error',
  234. {
  235. paths: Object.values(restrictedImportsRules).filter(Boolean),
  236. patterns: ['antd/*'],
  237. },
  238. ],
  239. // Temporarily disabled for migration
  240. 'no-unsafe-optional-chaining': 0,
  241. 'no-import-assign': 0,
  242. 'import/no-relative-packages': 0,
  243. 'no-promise-executor-return': 0,
  244. 'import/no-import-module-exports': 0,
  245. // Restrict certain syntax patterns
  246. 'no-restricted-syntax': [
  247. 'error',
  248. {
  249. selector:
  250. "ImportDeclaration[source.value='react'] :matches(ImportDefaultSpecifier, ImportNamespaceSpecifier)",
  251. message:
  252. 'Default React import is not required due to automatic JSX runtime in React 16.4',
  253. },
  254. {
  255. selector: 'ImportNamespaceSpecifier[parent.source.value!=/^(\\.|src)/]',
  256. message: 'Wildcard imports are not allowed',
  257. },
  258. ],
  259. },
  260. overrides: [
  261. {
  262. files: ['*.ts', '*.tsx'],
  263. parser: '@typescript-eslint/parser',
  264. parserOptions: {
  265. ecmaFeatures: {
  266. jsx: true,
  267. },
  268. tsconfigRootDir: __dirname,
  269. project: ['./tsconfig.json'],
  270. },
  271. extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
  272. plugins: ['@typescript-eslint/eslint-plugin'],
  273. rules: {
  274. // TypeScript-specific rule overrides
  275. '@typescript-eslint/ban-ts-ignore': 0,
  276. '@typescript-eslint/ban-ts-comment': 0,
  277. '@typescript-eslint/ban-types': 0,
  278. '@typescript-eslint/naming-convention': [
  279. 'error',
  280. {
  281. selector: 'enum',
  282. format: ['PascalCase'],
  283. },
  284. {
  285. selector: 'enumMember',
  286. format: ['PascalCase'],
  287. },
  288. ],
  289. '@typescript-eslint/no-empty-function': 0,
  290. '@typescript-eslint/no-explicit-any': 0,
  291. '@typescript-eslint/no-use-before-define': 1,
  292. '@typescript-eslint/no-non-null-assertion': 0,
  293. '@typescript-eslint/explicit-function-return-type': 0,
  294. '@typescript-eslint/explicit-module-boundary-types': 0,
  295. '@typescript-eslint/no-unused-vars': 'warn',
  296. '@typescript-eslint/prefer-optional-chain': 'error',
  297. // Disable base rules that conflict with TS versions
  298. 'no-unused-vars': 'off',
  299. 'no-use-before-define': 'off',
  300. 'no-shadow': 'off',
  301. // Import overrides for TypeScript
  302. 'import/extensions': [
  303. 'error',
  304. 'ignorePackages',
  305. {
  306. js: 'never',
  307. jsx: 'never',
  308. ts: 'never',
  309. tsx: 'never',
  310. },
  311. ],
  312. },
  313. settings: {
  314. 'import/resolver': {
  315. typescript: {},
  316. },
  317. },
  318. },
  319. {
  320. files: ['packages/**'],
  321. rules: {
  322. 'import/no-extraneous-dependencies': [
  323. 'error',
  324. { devDependencies: true },
  325. ],
  326. 'no-restricted-imports': [
  327. 'error',
  328. {
  329. paths: [
  330. restrictedImportsRules['no-moment'],
  331. restrictedImportsRules['no-lodash-memoize'],
  332. restrictedImportsRules['no-superset-theme'],
  333. ],
  334. patterns: [],
  335. },
  336. ],
  337. },
  338. },
  339. {
  340. files: ['plugins/**'],
  341. rules: {
  342. 'no-restricted-imports': [
  343. 'error',
  344. {
  345. paths: [
  346. restrictedImportsRules['no-moment'],
  347. restrictedImportsRules['no-lodash-memoize'],
  348. ],
  349. patterns: [],
  350. },
  351. ],
  352. },
  353. },
  354. {
  355. files: ['src/components/**', 'src/theme/**'],
  356. rules: {
  357. 'no-restricted-imports': [
  358. 'error',
  359. {
  360. paths: Object.values(restrictedImportsRules).filter(
  361. r => r.name !== 'antd',
  362. ),
  363. patterns: [],
  364. },
  365. ],
  366. },
  367. },
  368. {
  369. files: [
  370. '*.test.ts',
  371. '*.test.tsx',
  372. '*.test.js',
  373. '*.test.jsx',
  374. '*.stories.tsx',
  375. '*.stories.jsx',
  376. 'fixtures.*',
  377. '**/test/**/*',
  378. '**/tests/**/*',
  379. 'spec/**/*',
  380. '**/fixtures/**/*',
  381. '**/__mocks__/**/*',
  382. '**/spec/**/*',
  383. ],
  384. excludedFiles: 'cypress-base/cypress/**/*',
  385. plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'],
  386. env: {
  387. 'jest/globals': true,
  388. },
  389. settings: {
  390. jest: {
  391. version: 'detect',
  392. },
  393. },
  394. extends: [
  395. 'plugin:jest/recommended',
  396. 'plugin:jest-dom/recommended',
  397. 'plugin:testing-library/react',
  398. ],
  399. rules: {
  400. 'import/no-extraneous-dependencies': [
  401. 'error',
  402. { devDependencies: true },
  403. ],
  404. 'jest/consistent-test-it': 'error',
  405. 'no-only-tests/no-only-tests': 'error',
  406. 'prefer-promise-reject-errors': 0,
  407. 'max-classes-per-file': 0,
  408. // Temporary for migration
  409. 'testing-library/await-async-queries': 0,
  410. 'testing-library/await-async-utils': 0,
  411. 'testing-library/no-await-sync-events': 0,
  412. 'testing-library/no-render-in-lifecycle': 0,
  413. 'testing-library/no-unnecessary-act': 0,
  414. 'testing-library/no-wait-for-multiple-assertions': 0,
  415. 'testing-library/prefer-screen-queries': 0,
  416. 'testing-library/await-async-events': 0,
  417. 'testing-library/no-node-access': 0,
  418. 'testing-library/no-wait-for-side-effects': 0,
  419. 'testing-library/prefer-presence-queries': 0,
  420. 'testing-library/render-result-naming-convention': 0,
  421. 'testing-library/no-container': 0,
  422. 'testing-library/prefer-find-by': 0,
  423. 'testing-library/no-manual-cleanup': 0,
  424. 'no-restricted-syntax': [
  425. 'error',
  426. {
  427. selector:
  428. "ImportDeclaration[source.value='react'] :matches(ImportDefaultSpecifier, ImportNamespaceSpecifier)",
  429. message:
  430. 'Default React import is not required due to automatic JSX runtime in React 16.4',
  431. },
  432. ],
  433. 'no-restricted-imports': 0,
  434. },
  435. },
  436. {
  437. files: [
  438. '*.test.ts',
  439. '*.test.tsx',
  440. '*.test.js',
  441. '*.test.jsx',
  442. '*.stories.tsx',
  443. '*.stories.jsx',
  444. 'fixtures.*',
  445. '**/test/**/*',
  446. '**/tests/**/*',
  447. 'spec/**/*',
  448. '**/fixtures/**/*',
  449. '**/__mocks__/**/*',
  450. '**/spec/**/*',
  451. 'cypress-base/cypress/**/*',
  452. 'Stories.tsx',
  453. 'packages/superset-ui-core/src/theme/index.tsx',
  454. ],
  455. rules: {
  456. 'theme-colors/no-literal-colors': 0,
  457. 'icons/no-fa-icons-usage': 0,
  458. 'i18n-strings/no-template-vars': 0,
  459. 'no-restricted-imports': 0,
  460. 'react/no-void-elements': 0,
  461. },
  462. },
  463. {
  464. files: [
  465. 'packages/**/*.stories.*',
  466. 'packages/**/*.overview.*',
  467. 'packages/**/fixtures.*',
  468. ],
  469. rules: {
  470. 'import/no-extraneous-dependencies': 'off',
  471. },
  472. },
  473. {
  474. files: ['playwright/**/*.ts', 'playwright/**/*.js'],
  475. rules: {
  476. 'import/no-extraneous-dependencies': [
  477. 'error',
  478. { devDependencies: true },
  479. ],
  480. },
  481. },
  482. ],
  483. ignorePatterns,
  484. };