Explorar o código

Stricter linter (#3095)

* enforce some eslint rules

* enforce accessibility linter rules

* harden lint rules with only 1 or 2 warnings

* fix remaining rules with less than 3 warnings

* fix e2e tests

* fix remaining rules with less than 4 warnings

* fix remaining rules with less than 6 warnings

* fix `shuffleTaglines`

* fix companion build
Antoine du Hamel %!s(int64=3) %!d(string=hai) anos
pai
achega
6b7ad5e7c7
Modificáronse 100 ficheiros con 469 adicións e 366 borrados
  1. 69 56
      .eslintrc.js
  2. 1 0
      bin/upload-to-cdn.js
  3. 1 1
      examples/angular-example/karma.conf.js
  4. 1 0
      examples/bundled/sw.js
  5. 27 27
      examples/custom-provider/server/customprovider.js
  6. 1 0
      examples/dev/sw.js
  7. 1 1
      examples/react-native-expo/babel.config.js
  8. 3 2
      examples/redux/main.js
  9. 3 1
      packages/@uppy/aws-s3-multipart/src/MultipartUploader.js
  10. 5 3
      packages/@uppy/aws-s3-multipart/src/index.js
  11. 6 8
      packages/@uppy/companion-client/src/Provider.js
  12. 2 2
      packages/@uppy/companion-client/src/SearchProvider.js
  13. 1 1
      packages/@uppy/companion/src/companion.js
  14. 2 0
      packages/@uppy/companion/src/server/helpers/request.js
  15. 1 1
      packages/@uppy/companion/src/server/provider/credentials.js
  16. 7 7
      packages/@uppy/companion/src/server/provider/index.js
  17. 2 1
      packages/@uppy/companion/src/server/provider/zoom/adapter.js
  18. 5 2
      packages/@uppy/companion/src/server/provider/zoom/index.js
  19. 9 5
      packages/@uppy/companion/src/standalone/index.js
  20. 1 0
      packages/@uppy/companion/src/standalone/start-server.js
  21. 1 0
      packages/@uppy/core/src/loggers.js
  22. 29 28
      packages/@uppy/dashboard/src/components/AddFiles.js
  23. 1 0
      packages/@uppy/dashboard/src/components/Dashboard.js
  24. 1 1
      packages/@uppy/dashboard/src/components/EditorPanel.js
  25. 19 19
      packages/@uppy/dashboard/src/components/FileCard/index.js
  26. 5 4
      packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js
  27. 3 1
      packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js
  28. 0 1
      packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.js
  29. 4 4
      packages/@uppy/dashboard/src/components/FileItem/index.js
  30. 1 0
      packages/@uppy/dashboard/src/components/FileItem/index.scss
  31. 2 1
      packages/@uppy/dashboard/src/components/FileList.js
  32. 65 39
      packages/@uppy/dashboard/src/index.js
  33. 1 0
      packages/@uppy/dashboard/src/utils/copyToClipboard.js
  34. 12 5
      packages/@uppy/dashboard/src/utils/createSuperFocus.js
  35. 11 5
      packages/@uppy/dashboard/src/utils/trapFocus.js
  36. 0 5
      packages/@uppy/golden-retriever/src/IndexedDBStore.js
  37. 1 4
      packages/@uppy/golden-retriever/src/ServiceWorker.js
  38. 1 1
      packages/@uppy/informer/src/TransitionGroup.js
  39. 1 1
      packages/@uppy/locales/src/ar_SA.js
  40. 1 1
      packages/@uppy/locales/src/bg_BG.js
  41. 1 1
      packages/@uppy/locales/src/cs_CZ.js
  42. 1 1
      packages/@uppy/locales/src/da_DK.js
  43. 1 1
      packages/@uppy/locales/src/de_DE.js
  44. 1 1
      packages/@uppy/locales/src/el_GR.js
  45. 1 1
      packages/@uppy/locales/src/en_US.js
  46. 1 1
      packages/@uppy/locales/src/es_ES.js
  47. 1 1
      packages/@uppy/locales/src/fa_IR.js
  48. 1 1
      packages/@uppy/locales/src/fi_FI.js
  49. 1 1
      packages/@uppy/locales/src/fr_FR.js
  50. 1 1
      packages/@uppy/locales/src/gl_ES.js
  51. 1 1
      packages/@uppy/locales/src/he_IL.js
  52. 1 1
      packages/@uppy/locales/src/hr_HR.js
  53. 1 1
      packages/@uppy/locales/src/hu_HU.js
  54. 1 1
      packages/@uppy/locales/src/id_ID.js
  55. 1 1
      packages/@uppy/locales/src/is_IS.js
  56. 1 1
      packages/@uppy/locales/src/it_IT.js
  57. 1 1
      packages/@uppy/locales/src/ja_JP.js
  58. 1 1
      packages/@uppy/locales/src/ko_KR.js
  59. 1 1
      packages/@uppy/locales/src/nb_NO.js
  60. 1 1
      packages/@uppy/locales/src/nl_NL.js
  61. 1 1
      packages/@uppy/locales/src/pl_PL.js
  62. 1 1
      packages/@uppy/locales/src/pt_BR.js
  63. 1 1
      packages/@uppy/locales/src/pt_PT.js
  64. 1 1
      packages/@uppy/locales/src/ro_RO.js
  65. 1 1
      packages/@uppy/locales/src/ru_RU.js
  66. 1 1
      packages/@uppy/locales/src/sk_SK.js
  67. 1 1
      packages/@uppy/locales/src/sr_RS_Cyrillic.js
  68. 1 1
      packages/@uppy/locales/src/sr_RS_Latin.js
  69. 1 1
      packages/@uppy/locales/src/sv_SE.js
  70. 1 1
      packages/@uppy/locales/src/th_TH.js
  71. 1 1
      packages/@uppy/locales/src/tr_TR.js
  72. 1 1
      packages/@uppy/locales/src/uk_UA.js
  73. 1 1
      packages/@uppy/locales/src/vi_VN.js
  74. 1 1
      packages/@uppy/locales/src/zh_CN.js
  75. 1 1
      packages/@uppy/locales/src/zh_TW.js
  76. 1 1
      packages/@uppy/locales/template.js
  77. 2 2
      packages/@uppy/provider-views/src/FooterActions.js
  78. 1 1
      packages/@uppy/provider-views/src/Item/components/ItemIcon.js
  79. 23 25
      packages/@uppy/provider-views/src/ProviderView/AuthView.js
  80. 19 14
      packages/@uppy/provider-views/src/ProviderView/ProviderView.js
  81. 20 14
      packages/@uppy/provider-views/src/SearchProviderView/SearchProviderView.js
  82. 5 5
      packages/@uppy/react-native/file-picker/index.js
  83. 0 3
      packages/@uppy/react-native/file-picker/instagram.js
  84. 1 0
      packages/@uppy/react-native/file-picker/url.js
  85. 1 1
      packages/@uppy/react/src/propTypes.js
  86. 3 0
      packages/@uppy/redux-dev-tools/src/index.js
  87. 3 4
      packages/@uppy/robodog/src/TransloaditResultsPlugin.js
  88. 2 0
      packages/@uppy/robodog/types/index.test-d.ts
  89. 2 1
      packages/@uppy/screen-capture/src/CaptureScreen.js
  90. 1 0
      packages/@uppy/screen-capture/src/StopWatch.js
  91. 5 2
      packages/@uppy/screen-capture/src/index.js
  92. 8 4
      packages/@uppy/status-bar/src/StatusBar.js
  93. 3 1
      packages/@uppy/status-bar/src/index.js
  94. 1 0
      packages/@uppy/status-bar/src/style.scss
  95. 2 2
      packages/@uppy/store-default/src/index.js
  96. 17 7
      packages/@uppy/store-redux/src/index.js
  97. 1 1
      packages/@uppy/store-redux/src/index.test.js
  98. 3 3
      packages/@uppy/thumbnail-generator/src/index.js
  99. 2 1
      packages/@uppy/transloadit/src/Assembly.js
  100. 1 1
      packages/@uppy/tus/src/getFingerprint.js

+ 69 - 56
.eslintrc.js

@@ -5,7 +5,7 @@
 const path = require('path')
 
 const svgPresentationAttributes = [
-  'alignment-baseline', 'baseline-shift', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolatio', 'color-interpolatio-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'transform', 'transform-origin', 'unicode-bidi', 'vector-effect', 'visibility', 'word-spacing', 'writing-mod',
+  'alignment-baseline', 'baseline-shift', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolatio', 'color-interpolatio-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'transform', 'transform-origin', 'unicode-bidi', 'vector-effect', 'visibility', 'word-spacing', 'writing-mod',
 ]
 
 module.exports = {
@@ -44,88 +44,89 @@ module.exports = {
   },
   rules: {
     // transloadit rules we are actually ok with in the uppy repo
-    'guard-for-in': ['off'],
-    'import/extensions': ['off'],
-    'strict': ['off'],
-    'key-spacing': ['off'],
+    'import/extensions': 'off',
+    'no-await-in-loop': 'off',
+    'object-shorthand': ['error', 'always'],
+    'strict': 'off',
+    'key-spacing': 'off',
+
+    // rules we want to enforce
+    'array-callback-return': 'error',
+    'implicit-arrow-linebreak': 'error',
+    'import/no-dynamic-require': 'error',
+    'import/no-extraneous-dependencies': 'error',
+    'max-len': 'error',
+    'no-empty': 'error',
+    'no-bitwise': 'error',
+    'no-continue': 'error',
+    'no-lonely-if': 'error',
+    'no-nested-ternary': 'error',
+    'no-restricted-properties': 'error',
+    'no-return-assign': 'error',
+    'no-underscore-dangle': 'error',
+    'no-useless-concat': 'error',
+    'no-var': 'error',
+    'node/handle-callback-err': 'error',
+    'prefer-destructuring': 'error',
+    'prefer-spread': 'error',
 
     // transloadit rules we would like to enforce in the future
     // but will require separate PRs to gradually get there
     // and so the meantime: just warn
-    'array-callback-return': ['warn'],
-    'block-scoped-var': ['warn'],
     'class-methods-use-this': ['warn'],
     'consistent-return': ['warn'],
     'default-case': ['warn'],
     'global-require': ['warn'],
-    'implicit-arrow-linebreak': ['warn'],
-    'import/no-dynamic-require': ['warn'],
     'import/no-unresolved': ['warn'],
     'import/order': ['warn'],
-    'jsx-a11y/alt-text': ['warn'],
-    'jsx-a11y/anchor-has-content': ['warn'],
-    'jsx-a11y/click-events-have-key-events': ['warn'],
-    'jsx-a11y/control-has-associated-label': ['warn'],
-    'jsx-a11y/label-has-associated-control': ['warn'],
-    'jsx-a11y/media-has-caption': ['warn'],
-    'jsx-a11y/mouse-events-have-key-events': ['warn'],
-    'jsx-a11y/no-interactive-element-to-noninteractive-role': ['warn'],
-    'jsx-a11y/no-noninteractive-element-interactions': ['warn'],
-    'jsx-a11y/no-static-element-interactions': ['warn'],
-    'no-await-in-loop': ['warn'],
-    'no-bitwise': ['warn'],
-    'no-continue': ['warn'],
-    'no-empty': ['warn'],
-    'no-lonely-if': ['warn'],
     'no-mixed-operators': ['warn'],
-    'no-nested-ternary': ['warn'],
     'no-param-reassign': ['warn'],
     'no-redeclare': ['warn'],
-    'no-restricted-globals': ['warn'],
-    'no-restricted-properties': ['warn'],
-    'no-restricted-syntax': ['warn'],
-    'no-return-assign': ['warn'],
     'no-shadow': ['warn'],
-    'no-underscore-dangle': ['warn'],
     'no-unused-expressions': ['warn'],
     'no-unused-vars': ['warn'],
     'no-use-before-define': ['warn'],
-    'no-useless-concat': ['warn'],
-    'no-var': ['warn'],
-    'node/handle-callback-err': ['warn'],
-    'prefer-destructuring': ['warn'],
-    'prefer-spread': ['warn'],
     'radix': ['warn'],
-    'react/button-has-type': ['warn'],
+    'react/button-has-type': 'error',
     'react/destructuring-assignment': ['warn'],
-    'react/forbid-prop-types': ['warn'],
+    'react/forbid-prop-types': 'error',
     'react/jsx-props-no-spreading': ['warn'],
-    'react/no-access-state-in-setstate': ['warn'],
-    'react/no-array-index-key': ['warn'],
-    'react/no-deprecated': ['warn'],
-    'react/no-this-in-sfc': ['warn'],
-    'react/no-will-update-set-state': ['warn'],
-    'react/prefer-stateless-function': ['warn'],
-    'react/sort-comp': ['warn'],
-    'react/style-prop-object': ['warn'],
-    'react/no-unknown-property': ['warn', {
+    'react/no-access-state-in-setstate': 'error',
+    'react/no-array-index-key': 'error',
+    'react/no-deprecated': 'error',
+    'react/no-this-in-sfc': 'error',
+    'react/no-will-update-set-state': 'error',
+    'react/prefer-stateless-function': 'error',
+    'react/sort-comp': 'error',
+    'react/style-prop-object': 'error',
+    'react/no-unknown-property': ['error', {
       ignore: svgPresentationAttributes,
     }],
-    'vars-on-top': ['warn'],
-    'import/no-extraneous-dependencies': ['error'],
+
+    // accessibility
+    'jsx-a11y/alt-text': 'error',
+    'jsx-a11y/anchor-has-content': 'error',
+    'jsx-a11y/click-events-have-key-events': 'error',
+    'jsx-a11y/control-has-associated-label': 'error',
+    'jsx-a11y/label-has-associated-control': 'error',
+    'jsx-a11y/media-has-caption': 'error',
+    'jsx-a11y/mouse-events-have-key-events': 'error',
+    'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
+    'jsx-a11y/no-noninteractive-element-interactions': 'error',
+    'jsx-a11y/no-static-element-interactions': 'error',
 
     // compat
     'compat/compat': ['error'],
 
     // jsdoc
-    'jsdoc/check-alignment': ['warn'],
-    'jsdoc/check-examples': ['warn'],
+    'jsdoc/check-alignment': 'error',
+    'jsdoc/check-examples': 'error',
     'jsdoc/check-param-names': ['warn'],
     'jsdoc/check-syntax': ['warn'],
-    'jsdoc/check-tag-names': ['warn'],
-    'jsdoc/check-types': ['warn'],
-    'jsdoc/newline-after-description': ['warn'],
-    'jsdoc/valid-types': ['warn'],
+    'jsdoc/check-tag-names': 'error',
+    'jsdoc/check-types': 'error',
+    'jsdoc/newline-after-description': 'error',
+    'jsdoc/valid-types': 'error',
     'jsdoc/check-indentation': ['off'],
   },
 
@@ -151,6 +152,13 @@ module.exports = {
   },
 
   overrides: [
+    {
+      files: ['./packages/@uppy/companion/**/*.js'],
+      rules: {
+        'no-restricted-syntax': 'warn',
+        'no-underscore-dangle': 'off',
+      },
+    },
     {
       files: [
         '*.test.js',
@@ -162,17 +170,22 @@ module.exports = {
         'compat/compat': ['off'],
       },
     },
-
     {
       files: [
         'bin/**.js',
+        'examples/**/*.js',
+        'packages/@uppy/companion/test/**/*.js',
+        'test/**/*.js',
+        'test/**/*.ts',
+        '*.test.js',
+        '*.test-d.ts',
         'postcss.config.js',
         '.eslintrc.js',
         'website/*.js',
         'website/**/*.js',
       ],
       rules: {
-        'no-console': ['off'],
+        'no-console': 'off',
         'import/no-extraneous-dependencies': ['error', {
           devDependencies: true,
         }],

+ 1 - 0
bin/upload-to-cdn.js

@@ -118,6 +118,7 @@ async function main (packageName, version) {
 
   const remote = !!version
   if (!remote) {
+    // eslint-disable-next-line import/no-dynamic-require
     version = require(`../packages/${packageName}/package.json`).version
   }
 

+ 1 - 1
examples/angular-example/karma.conf.js

@@ -1,7 +1,7 @@
 // Karma configuration file, see link for more information
 // https://karma-runner.github.io/1.0/config/configuration-file.html
 
-module.exports = function (config) {
+module.exports = function karma (config) {
   config.set({
     basePath: '',
     frameworks: ['jasmine', '@angular-devkit/build-angular'],

+ 1 - 0
examples/bundled/sw.js

@@ -3,6 +3,7 @@
 // https://uppy.io/docs/golden-retriever/
 
 /* globals clients */
+/* eslint-disable no-restricted-globals */
 
 const fileCache = Object.create(null)
 

+ 27 - 27
examples/custom-provider/server/customprovider.js

@@ -2,6 +2,32 @@ const request = require('request')
 
 const BASE_URL = 'https://api.unsplash.com'
 
+function adaptData (res) {
+  const data = {
+    username: null,
+    items: [],
+    nextPagePath: null,
+  }
+
+  const items = res
+  items.forEach((item) => {
+    const isFolder = !!item.published_at
+    data.items.push({
+      isFolder,
+      icon: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
+      name: item.title || item.description,
+      mimeType: isFolder ? null : 'image/jpeg',
+      id: item.id,
+      thumbnail: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
+      requestPath: item.id,
+      modifiedDate: item.updated_at,
+      size: null,
+    })
+  })
+
+  return data
+}
+
 /**
  * an example of a custom provider module. It implements @uppy/companion's Provider interface
  */
@@ -28,7 +54,7 @@ class MyCustomProvider {
         return
       }
 
-      done(null, this._adaptData(body))
+      done(null, adaptData(body))
     })
   }
 
@@ -76,32 +102,6 @@ class MyCustomProvider {
       done(null, body.width * body.height)
     })
   }
-
-  _adaptData (res) {
-    const data = {
-      username: null,
-      items: [],
-      nextPagePath: null,
-    }
-
-    const items = res
-    items.forEach((item) => {
-      const isFolder = !!item.published_at
-      data.items.push({
-        isFolder,
-        icon: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
-        name: item.title || item.description,
-        mimeType: isFolder ? null : 'image/jpeg',
-        id: item.id,
-        thumbnail: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
-        requestPath: item.id,
-        modifiedDate: item.updated_at,
-        size: null,
-      })
-    })
-
-    return data
-  }
 }
 
 module.exports = MyCustomProvider

+ 1 - 0
examples/dev/sw.js

@@ -1,4 +1,5 @@
 /* globals clients */
+/* eslint-disable no-restricted-globals */
 
 const fileCache = Object.create(null)
 

+ 1 - 1
examples/react-native-expo/babel.config.js

@@ -1,4 +1,4 @@
-module.exports = function (api) {
+module.exports = function babel (api) {
   api.cache(true)
   return {
     presets: ['babel-preset-expo'],

+ 3 - 2
examples/redux/main.js

@@ -27,8 +27,9 @@ let enhancer = applyMiddleware(
   uppyReduxStore.middleware(),
   logger
 )
-if (window.__REDUX_DEVTOOLS_EXTENSION__) {
-  enhancer = compose(enhancer, window.__REDUX_DEVTOOLS_EXTENSION__())
+if (typeof __REDUX_DEVTOOLS_EXTENSION__ !== 'undefined') {
+  // eslint-disable-next-line no-undef
+  enhancer = compose(enhancer, __REDUX_DEVTOOLS_EXTENSION__())
 }
 
 const store = createStore(reducer, enhancer)

+ 3 - 1
packages/@uppy/aws-s3-multipart/src/MultipartUploader.js

@@ -181,8 +181,10 @@ class MultipartUploader {
 
     const candidates = []
     for (let i = 0; i < this.chunkState.length; i++) {
+      // eslint-disable-next-line no-continue
       if (this.lockedCandidatesForBatch.includes(i)) continue
       const state = this.chunkState[i]
+      // eslint-disable-next-line no-continue
       if (state.done || state.busy) continue
 
       candidates.push(i)
@@ -319,7 +321,7 @@ class MultipartUploader {
     const xhr = new XMLHttpRequest()
     xhr.open('PUT', url, true)
     if (headers) {
-      Object.keys(headers).map((key) => {
+      Object.keys(headers).forEach((key) => {
         xhr.setRequestHeader(key, headers[key])
       })
     }

+ 5 - 3
packages/@uppy/aws-s3-multipart/src/index.js

@@ -80,7 +80,7 @@ module.exports = class AwsS3Multipart extends BasePlugin {
 
     const metadata = {}
 
-    Object.keys(file.meta).map(key => {
+    Object.keys(file.meta).forEach(key => {
       if (file.meta[key] != null) {
         metadata[key] = file.meta[key].toString()
       }
@@ -239,7 +239,8 @@ module.exports = class AwsS3Multipart extends BasePlugin {
           queuedRequest.abort()
           upload.pause()
         } else {
-          // Resuming an upload should be queued, else you could pause and then resume a queued upload to make it skip the queue.
+          // Resuming an upload should be queued, else you could pause and then
+          // resume a queued upload to make it skip the queue.
           queuedRequest.abort()
           queuedRequest = this.requests.run(() => {
             upload.start()
@@ -330,7 +331,8 @@ module.exports = class AwsS3Multipart extends BasePlugin {
           queuedRequest.abort()
           socket.send('pause', {})
         } else {
-          // Resuming an upload should be queued, else you could pause and then resume a queued upload to make it skip the queue.
+          // Resuming an upload should be queued, else you could pause and then
+          // resume a queued upload to make it skip the queue.
           queuedRequest.abort()
           queuedRequest = this.requests.run(() => {
             socket.send('resume', {})

+ 6 - 8
packages/@uppy/companion-client/src/Provider.js

@@ -3,7 +3,7 @@
 const RequestClient = require('./RequestClient')
 const tokenStorage = require('./tokenStorage')
 
-const _getName = (id) => {
+const getName = (id) => {
   return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
 }
 
@@ -12,7 +12,7 @@ module.exports = class Provider extends RequestClient {
     super(uppy, opts)
     this.provider = opts.provider
     this.id = this.provider
-    this.name = this.opts.name || _getName(this.id)
+    this.name = this.opts.name || getName(this.id)
     this.pluginId = this.opts.pluginId
     this.tokenKey = `companion-${this.pluginId}-auth-token`
     this.companionKeysParams = this.opts.companionKeysParams
@@ -108,13 +108,11 @@ module.exports = class Provider extends RequestClient {
         throw new TypeError(`${plugin.id}: the option "companionAllowedHosts" must be one of string, Array, RegExp`)
       }
       plugin.opts.companionAllowedHosts = pattern
-    } else {
+    } else if (/^(?!https?:\/\/).*$/i.test(opts.companionUrl)) {
       // does not start with https://
-      if (/^(?!https?:\/\/).*$/i.test(opts.companionUrl)) {
-        plugin.opts.companionAllowedHosts = `https://${opts.companionUrl.replace(/^\/\//, '')}`
-      } else {
-        plugin.opts.companionAllowedHosts = new URL(opts.companionUrl).origin
-      }
+      plugin.opts.companionAllowedHosts = `https://${opts.companionUrl.replace(/^\/\//, '')}`
+    } else {
+      plugin.opts.companionAllowedHosts = new URL(opts.companionUrl).origin
     }
 
     plugin.storage = plugin.opts.storage || tokenStorage

+ 2 - 2
packages/@uppy/companion-client/src/SearchProvider.js

@@ -2,7 +2,7 @@
 
 const RequestClient = require('./RequestClient')
 
-const _getName = (id) => {
+const getName = (id) => {
   return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
 }
 
@@ -11,7 +11,7 @@ module.exports = class SearchProvider extends RequestClient {
     super(uppy, opts)
     this.provider = opts.provider
     this.id = this.provider
-    this.name = this.opts.name || _getName(this.id)
+    this.name = this.opts.name || getName(this.id)
     this.pluginId = this.opts.pluginId
   }
 

+ 1 - 1
packages/@uppy/companion/src/companion.js

@@ -233,7 +233,7 @@ const validateConfig = (companionOptions) => {
   // validate that specified filePath is writeable/readable.
   try {
     // @ts-ignore
-    fs.accessSync(`${companionOptions.filePath}`, fs.R_OK | fs.W_OK)
+    fs.accessSync(`${companionOptions.filePath}`, fs.R_OK | fs.W_OK) // eslint-disable-line no-bitwise
   } catch (err) {
     throw new Error(
       `No access to "${companionOptions.filePath}". Please ensure the directory exists and with read/write permissions.`

+ 2 - 0
packages/@uppy/companion/src/server/helpers/request.js

@@ -14,6 +14,7 @@ function isIPAddress (address) {
   return addressAsV6.isValid() || addressAsV4.isValid()
 }
 
+/* eslint-disable max-len */
 /**
  * Determine if a IP address provided is a private one.
  * Return TRUE if it's the case, FALSE otherwise.
@@ -23,6 +24,7 @@ function isIPAddress (address) {
  * @param {string} ipAddress the ip address to validate
  * @returns {boolean}
  */
+/* eslint-enable max-len */
 function isPrivateIP (ipAddress) {
   let isPrivate = false
   // Build the list of IP prefix for V4 and V6 addresses

+ 1 - 1
packages/@uppy/companion/src/server/provider/credentials.js

@@ -13,7 +13,7 @@ const Provider = require('./Provider')
  *
  * @param {Object.<string, (typeof Provider)>} providers provider classes enabled for this server
  * @param {object} companionOptions companion options object
- * @returns {(req: object, res: object, next: function()) => void}
+ * @returns {(req: object, res: object, next: Function) => void}
  */
 exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {
   return (req, res, next) => {

+ 7 - 7
packages/@uppy/companion/src/server/provider/index.js

@@ -48,8 +48,8 @@ config.zoom = {
  * adds the desired provider module to the request object,
  * based on the providerName parameter specified
  *
- * @param {Object.<string, (typeof Provider) | typeof SearchProvider>} providers
- * @param {boolean=} needsProviderCredentials
+ * @param {Record<string, (typeof Provider) | typeof SearchProvider>} providers
+ * @param {boolean} [needsProviderCredentials]
  */
 module.exports.getProviderMiddleware = (providers, needsProviderCredentials) => {
   /**
@@ -75,7 +75,7 @@ module.exports.getProviderMiddleware = (providers, needsProviderCredentials) =>
 }
 
 /**
- * @returns {Object.<string, typeof Provider>}
+ * @returns {Record<string, typeof Provider>}
  */
 module.exports.getDefaultProviders = () => {
   const providers = { dropbox, box, drive, facebook, onedrive, zoom, instagram }
@@ -84,7 +84,7 @@ module.exports.getDefaultProviders = () => {
 }
 
 /**
- * @returns {Object.<string, typeof SearchProvider>}
+ * @returns {Record<string, typeof SearchProvider>}
  */
 module.exports.getSearchProviders = () => {
   return { unsplash }
@@ -92,10 +92,10 @@ module.exports.getSearchProviders = () => {
 
 /**
  *
- * @typedef {{module: typeof Provider, config: object}} CustomProvider
+ * @typedef {{'module': typeof Provider, config: Record<string,unknown>}} CustomProvider
  *
- * @param {Object.<string, CustomProvider>} customProviders
- * @param {Object.<string, typeof Provider>} providers
+ * @param {Record<string, CustomProvider>} customProviders
+ * @param {Record<string, typeof Provider>} providers
  * @param {object} grantConfig
  */
 module.exports.addCustomProviders = (customProviders, providers, grantConfig) => {

+ 2 - 1
packages/@uppy/companion/src/server/provider/zoom/adapter.js

@@ -107,7 +107,8 @@ exports.getRequestPath = (item) => {
   } if (item.file_type) {
     return `${encodeURIComponent(item.meeting_id)}?recordingId=${encodeURIComponent(item.id)}`
   }
-  // Zoom meeting ids are reused so we need to use the UUID. Also, these UUIDs can contain `/` characters which require double encoding (see https://devforum.zoom.us/t/double-encode-meeting-uuids/23729)
+  // Zoom meeting ids are reused so we need to use the UUID. Also, these UUIDs can contain `/` characters which require
+  // double encoding (see https://devforum.zoom.us/t/double-encode-meeting-uuids/23729).
   return `${encodeURIComponent(encodeURIComponent(item.uuid))}`
 }
 

+ 5 - 2
packages/@uppy/companion/src/server/provider/zoom/index.js

@@ -213,7 +213,8 @@ class Zoom extends Provider {
       return { items: [] }
     }
 
-    // we query the zoom api by date (from 00:00 - 23:59 UTC) which may include extra results for 00:00 - 23:59 local time that we want to filter out
+    // we query the zoom api by date (from 00:00 - 23:59 UTC) which may include
+    // extra results for 00:00 - 23:59 local time that we want to filter out.
     const utcFrom = moment.tz(query.from, userResponse.timezone || 'UTC').startOf('day').tz('UTC')
     const utcTo = moment.tz(query.to, userResponse.timezone || 'UTC').endOf('day').tz('UTC')
 
@@ -320,7 +321,9 @@ class Zoom extends Provider {
     if (resp) {
       const fallbackMsg = `request to ${this.authProvider} returned ${resp.statusCode}`
       const errMsg = (resp.body || {}).message ? resp.body.message : fallbackMsg
-      return authErrorCodes.indexOf(resp.statusCode) > -1 ? new ProviderAuthError() : new ProviderApiError(errMsg, resp.statusCode)
+      return authErrorCodes.indexOf(resp.statusCode) > -1
+        ? new ProviderAuthError()
+        : new ProviderApiError(errMsg, resp.statusCode)
     }
     return err
   }

+ 9 - 5
packages/@uppy/companion/src/standalone/index.js

@@ -29,16 +29,17 @@ module.exports = function server (inputCompanionOptions = {}) {
    *
    * Returns a copy of the object with unknown types removed and sensitive values replaced by ***.
    *
-   * The input type is more broad that it needs to be, this way typescript can help us guarantee that we're dealing with all possible inputs :)
+   * The input type is more broad that it needs to be, this way typescript can help us guarantee that we're dealing with all
+   * possible inputs :)
    *
-   * @param {{ [key: string]: any }} rawQuery
+   * @param {Record<string, any>} rawQuery
    * @returns {{
-   *   query: { [key: string]: string },
+   *   query: Record<string, any>,
    *   censored: boolean
    * }}
    */
   function censorQuery (rawQuery) {
-    /** @type {{ [key: string]: string }} */
+    /** @type {Record<string, any>} */
     const query = {}
     let censored = false
     Object.keys(rawQuery).forEach((key) => {
@@ -82,7 +83,8 @@ module.exports = function server (inputCompanionOptions = {}) {
   // for server metrics tracking.
   // make app metrics available at '/metrics'.
   // TODO for the next major version: use instead companion option "metrics": true and remove this code
-  // Se discussion: https://github.com/transloadit/uppy/pull/2854/files/64be97205e4012818abfcc8b0b8b7fe09de91729#diff-68f5e3eb307c1c9d1fd02224fd7888e2f74718744e1b6e35d929fcab1cc50ed1
+  // eslint-disable-next-line max-len
+  // See discussion: https://github.com/transloadit/uppy/pull/2854/files/64be97205e4012818abfcc8b0b8b7fe09de91729#diff-68f5e3eb307c1c9d1fd02224fd7888e2f74718744e1b6e35d929fcab1cc50ed1
   if (process.env.COMPANION_HIDE_METRICS !== 'true') {
     app.use(middlewares.metrics())
   }
@@ -152,6 +154,7 @@ module.exports = function server (inputCompanionOptions = {}) {
     // initialize companion
     companionApp = companion.app(companionOptions)
   } catch (error) {
+    // eslint-disable-next-line no-console
     console.error('\x1b[31m', error.message, '\x1b[0m')
     process.exit(1)
   }
@@ -177,6 +180,7 @@ module.exports = function server (inputCompanionOptions = {}) {
       })
       res.header('Content-Length', `${Buffer.byteLength(content, 'utf8')}`)
       // use writeHead to prevent 'charset' from being appended
+      // eslint-disable-next-line max-len
       // https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-configure-publisher-domain#to-select-a-verified-domain
       res.writeHead(200, { 'Content-Type': 'application/json' })
       res.write(content)

+ 1 - 0
packages/@uppy/companion/src/standalone/start-server.js

@@ -10,5 +10,6 @@ const { app } = standalone()
 
 companion.socket(app.listen(port))
 
+/* eslint-disable no-console */
 console.log(`Welcome to Companion! v${version}`)
 console.log(`Listening on http://0.0.0.0:${port}`)

+ 1 - 0
packages/@uppy/core/src/loggers.js

@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
 const getTimeStamp = require('@uppy/utils/lib/getTimeStamp')
 
 // Swallow all logs, except errors.

+ 29 - 28
packages/@uppy/dashboard/src/components/AddFiles.js

@@ -21,33 +21,6 @@ class AddFiles extends Component {
     event.target.value = null
   }
 
-  renderPoweredByUppy () {
-    const { i18nArray } = this.props
-
-    const uppyBranding = (
-      <span>
-        <svg aria-hidden="true" focusable="false" className="uppy-c-icon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
-          <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fillRule="evenodd" />
-        </svg>
-        <span className="uppy-Dashboard-poweredByUppy">Uppy</span>
-      </span>
-    )
-
-    const linkText = i18nArray('poweredBy', { uppy: uppyBranding })
-
-    return (
-      <a
-        tabIndex="-1"
-        href="https://uppy.io"
-        rel="noreferrer noopener"
-        target="_blank"
-        className="uppy-Dashboard-poweredBy"
-      >
-        {linkText}
-      </a>
-    )
-  }
-
   renderHiddenInput = (isFolder, refCallback) => {
     return (
       <input
@@ -120,6 +93,7 @@ class AddFiles extends Component {
     return (
       <div class="uppy-Dashboard-AddFiles-title">
         {
+          // eslint-disable-next-line no-nested-ternary
           this.props.disableLocalFiles ? this.props.i18n('importFiles')
             : numberOfAcquirers > 0
               ? this.props.i18nArray(`dropPasteImport${camelFMSelectionType}`, { browseFiles, browseFolders, browse: browseFiles })
@@ -173,13 +147,40 @@ class AddFiles extends Component {
       <div className="uppy-Dashboard-AddFiles-list" role="tablist">
         {!disableLocalFiles && this.renderMyDeviceAcquirer()}
         {acquirersWithoutLastTwo.map((acquirer) => this.renderAcquirer(acquirer))}
-        <span role="presentation" style="white-space: nowrap;">
+        <span role="presentation" style={{ 'white-space': 'nowrap' }}>
           {lastTwoAcquirers.map((acquirer) => this.renderAcquirer(acquirer))}
         </span>
       </div>
     )
   }
 
+  renderPoweredByUppy () {
+    const { i18nArray } = this.props
+
+    const uppyBranding = (
+      <span>
+        <svg aria-hidden="true" focusable="false" className="uppy-c-icon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
+          <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fillRule="evenodd" />
+        </svg>
+        <span className="uppy-Dashboard-poweredByUppy">Uppy</span>
+      </span>
+    )
+
+    const linkText = i18nArray('poweredBy', { uppy: uppyBranding })
+
+    return (
+      <a
+        tabIndex="-1"
+        href="https://uppy.io"
+        rel="noreferrer noopener"
+        target="_blank"
+        className="uppy-Dashboard-poweredBy"
+      >
+        {linkText}
+      </a>
+    )
+  }
+
   render () {
     return (
       <div className="uppy-Dashboard-AddFiles">

+ 1 - 0
packages/@uppy/dashboard/src/components/Dashboard.js

@@ -81,6 +81,7 @@ module.exports = function Dashboard (props) {
       onDrop={props.handleDrop}
     >
       <div
+        aria-hidden="true"
         className="uppy-Dashboard-overlay"
         tabIndex={-1}
         onClick={props.handleClickOutside}

+ 1 - 1
packages/@uppy/dashboard/src/components/EditorPanel.js

@@ -2,7 +2,7 @@ const { h } = require('preact')
 const classNames = require('classnames')
 
 function EditorPanel (props) {
-  const file = this.props.files[this.props.fileCardFor]
+  const file = props.files[props.fileCardFor]
 
   return (
     <div

+ 19 - 19
packages/@uppy/dashboard/src/components/FileCard/index.js

@@ -6,6 +6,8 @@ const ignoreEvent = require('../../utils/ignoreEvent.js')
 const FilePreview = require('../FilePreview')
 
 class FileCard extends Component {
+  form = document.createElement('form');
+
   constructor (props) {
     super(props)
 
@@ -24,6 +26,23 @@ class FileCard extends Component {
     this.form.id = nanoid()
   }
 
+  // TODO(aduh95): move this to `UNSAFE_componentWillMount` when updating to Preact X+.
+  componentWillMount () { // eslint-disable-line react/no-deprecated
+    this.form.addEventListener('submit', this.handleSave)
+    document.body.appendChild(this.form)
+  }
+
+  componentWillUnmount () {
+    this.form.removeEventListener('submit', this.handleSave)
+    document.body.removeChild(this.form)
+  }
+
+  getMetaFields () {
+    return typeof this.props.metaFields === 'function'
+      ? this.props.metaFields(this.props.files[this.props.fileCardFor])
+      : this.props.metaFields
+  }
+
   updateMeta = (newVal, name) => {
     this.setState(({ formState }) => ({
       formState: {
@@ -33,8 +52,6 @@ class FileCard extends Component {
     }))
   }
 
-  form = document.createElement('form');
-
   handleSave = (e) => {
     e.preventDefault()
     const fileID = this.props.fileCardFor
@@ -45,17 +62,6 @@ class FileCard extends Component {
     this.props.toggleFileCard(false)
   }
 
-  // TODO(aduh95): move this to `UNSAFE_componentWillMount` when updating to Preact X+.
-  componentWillMount () {
-    this.form.addEventListener('submit', this.handleSave)
-    document.body.appendChild(this.form)
-  }
-
-  componentWillUnmount () {
-    this.form.removeEventListener('submit', this.handleSave)
-    document.body.removeChild(this.form)
-  }
-
   renderMetaFields = () => {
     const metaFields = this.getMetaFields() || []
     const fieldCSSClasses = {
@@ -94,12 +100,6 @@ class FileCard extends Component {
     })
   }
 
-  getMetaFields () {
-    return typeof this.props.metaFields === 'function'
-      ? this.props.metaFields(this.props.files[this.props.fileCardFor])
-      : this.props.metaFields
-  }
-
   render () {
     const file = this.props.files[this.props.fileCardFor]
     const showEditButton = this.props.canEditFile(file)

+ 5 - 4
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js

@@ -51,16 +51,16 @@ const ReSelectButton = (props) => (
 const ErrorButton = ({ file, onClick }) => {
   if (file.error) {
     return (
-      <span
+      <button
         className="uppy-Dashboard-Item-errorDetails"
         aria-label={file.error}
         data-microtip-position="bottom"
         data-microtip-size="medium"
-        role="tooltip"
         onClick={onClick}
+        type="button"
       >
         ?
-      </span>
+      </button>
     )
   }
   return null
@@ -75,7 +75,8 @@ module.exports = function FileInfo (props) {
         {ReSelectButton(props)}
         <ErrorButton
           file={props.file}
-          onClick={() => alert(props.file.error)}
+          // eslint-disable-next-line no-alert
+          onClick={() => alert(props.file.error)} // TODO: move to a custom alert implementation
         />
       </div>
     </div>

+ 3 - 1
packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js

@@ -18,7 +18,9 @@ module.exports = function FilePreviewAndLink (props) {
             rel="noreferrer noopener"
             target="_blank"
             aria-label={props.file.meta.name}
-          />
+          >
+            <span hidden>props.file.meta.name</span>
+          </a>
           )
       }
       <FilePreview file={props.file} />

+ 0 - 1
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.js

@@ -1,7 +1,6 @@
 const { h } = require('preact')
 
 function onPauseResumeCancelRetry (props) {
-  console.log(props.uppy)
   if (props.isUploaded) return
 
   if (props.error && !props.hideRetryButton) {

+ 4 - 4
packages/@uppy/dashboard/src/components/FileItem/index.js

@@ -7,10 +7,6 @@ const FileInfo = require('./FileInfo')
 const Buttons = require('./Buttons')
 
 module.exports = class FileItem extends Component {
-  shouldComponentUpdate (nextProps) {
-    return !shallowEqual(this.props, nextProps)
-  }
-
   componentDidMount () {
     const { file } = this.props
     if (!file.preview) {
@@ -18,6 +14,10 @@ module.exports = class FileItem extends Component {
     }
   }
 
+  shouldComponentUpdate (nextProps) {
+    return !shallowEqual(this.props, nextProps)
+  }
+
   // VirtualList mounts FileItems again and they emit `thumbnail:request`
   // Otherwise thumbnails are broken or missing after Golden Retriever restores files
   componentDidUpdate () {

+ 1 - 0
packages/@uppy/dashboard/src/components/FileItem/index.scss

@@ -141,6 +141,7 @@
 }
 
 .uppy-Dashboard-Item-errorDetails {
+  appearance: none;
   line-height: 12px;
   width: 12px;
   height: 12px;

+ 2 - 1
packages/@uppy/dashboard/src/components/FileList.js

@@ -70,7 +70,8 @@ module.exports = (props) => {
 
   function renderRow (row) {
     return (
-      // The `role="presentation` attribute ensures that the list items are properly associated with the `VirtualList` element
+      // The `role="presentation` attribute ensures that the list items are properly
+      // associated with the `VirtualList` element.
       // We use the first file ID as the key—this should not change across scroll rerenders
       <div role="presentation" key={row[0]}>
         {row.map((fileID) => (

+ 65 - 39
packages/@uppy/dashboard/src/index.js

@@ -1,6 +1,5 @@
 const { h } = require('preact')
 const { UIPlugin } = require('@uppy/core')
-const Translator = require('@uppy/utils/lib/Translator')
 const StatusBar = require('@uppy/status-bar')
 const Informer = require('@uppy/informer')
 const ThumbnailGenerator = require('@uppy/thumbnail-generator')
@@ -41,6 +40,8 @@ function defaultPickerIcon () {
 module.exports = class Dashboard extends UIPlugin {
   static VERSION = require('../package.json').version
 
+  #openFileEditorWhenFilesAdded
+
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'Dashboard'
@@ -241,7 +242,7 @@ module.exports = class Dashboard extends UIPlugin {
 
   canEditFile = (file) => {
     const { targets } = this.getPluginState()
-    const editors = this._getEditors(targets)
+    const editors = this.#getEditors(targets)
 
     return editors.some((target) => (
       this.uppy.getPlugin(target.id).canEditFile(file)
@@ -250,7 +251,7 @@ module.exports = class Dashboard extends UIPlugin {
 
   openFileEditor = (file) => {
     const { targets } = this.getPluginState()
-    const editors = this._getEditors(targets)
+    const editors = this.#getEditors(targets)
 
     this.setPluginState({
       showFileEditor: true,
@@ -265,7 +266,7 @@ module.exports = class Dashboard extends UIPlugin {
 
   saveFileEditor = () => {
     const { targets } = this.getPluginState()
-    const editors = this._getEditors(targets)
+    const editors = this.#getEditors(targets)
 
     editors.forEach((editor) => {
       this.uppy.getPlugin(editor.id).save()
@@ -315,7 +316,8 @@ module.exports = class Dashboard extends UIPlugin {
 
   closeModal = (opts = {}) => {
     const {
-      manualClose = true, // Whether the modal is being closed by the user (`true`) or by other means (e.g. browser back button)
+      // Whether the modal is being closed by the user (`true`) or by other means (e.g. browser back button)
+      manualClose = true,
     } = opts
 
     const { isHidden, isClosing } = this.getPluginState()
@@ -364,9 +366,11 @@ module.exports = class Dashboard extends UIPlugin {
     if (manualClose) {
       if (this.opts.browserBackButtonClose) {
         // Make sure that the latest entry in the history state is our modal name
-        if (history.state && history.state[this.modalName]) {
+        // eslint-disable-next-line no-restricted-globals
+        if (history.state?.[this.modalName]) {
           // Go back in history to clear out the entry we created (ultimately closing the modal)
-          history.go(-1)
+          // eslint-disable-next-line no-restricted-globals
+          history.back()
         }
       }
     }
@@ -445,9 +449,11 @@ module.exports = class Dashboard extends UIPlugin {
   }
 
   // ___Why make insides of Dashboard invisible until first ResizeObserver event is emitted?
-  //    ResizeOberserver doesn't emit the first resize event fast enough, users can see the jump from one .uppy-size-- to another (e.g. in Safari)
+  //    ResizeOberserver doesn't emit the first resize event fast enough, users can see the jump from one .uppy-size-- to
+  //    another (e.g. in Safari)
   // ___Why not apply visibility property to .uppy-Dashboard-inner?
-  //    Because ideally, acc to specs, ResizeObserver should see invisible elements as of width 0. So even though applying invisibility to .uppy-Dashboard-inner works now, it may not work in the future.
+  //    Because ideally, acc to specs, ResizeObserver should see invisible elements as of width 0. So even though applying
+  //    invisibility to .uppy-Dashboard-inner works now, it may not work in the future.
   startListeningToResize = () => {
     // Watch for Dashboard container (`.uppy-Dashboard-inner`) resize
     // and update containerWidth/containerHeight in plugin state accordingly.
@@ -531,9 +537,12 @@ module.exports = class Dashboard extends UIPlugin {
 
   updateBrowserHistory = () => {
     // Ensure history state does not already contain our modal name to avoid double-pushing
-    if (!history.state || !history.state[this.modalName]) {
+    // eslint-disable-next-line no-restricted-globals
+    if (!history.state?.[this.modalName]) {
       // Push to history so that the page is not lost on browser back button press
+      // eslint-disable-next-line no-restricted-globals
       history.pushState({
+        // eslint-disable-next-line no-restricted-globals
         ...history.state,
         [this.modalName]: true,
       }, '')
@@ -549,11 +558,15 @@ module.exports = class Dashboard extends UIPlugin {
       this.closeModal({ manualClose: false })
     }
 
-    // When the browser back button is pressed and uppy is now the latest entry in the history but the modal is closed, fix the history by removing the uppy history entry
-    // This occurs when another entry is added into the history state while the modal is open, and then the modal gets manually closed
+    // When the browser back button is pressed and uppy is now the latest entry
+    // in the history but the modal is closed, fix the history by removing the
+    // uppy history entry.
+    // This occurs when another entry is added into the history state while the
+    // modal is open, and then the modal gets manually closed.
     // Solves PR #575 (https://github.com/transloadit/uppy/pull/575)
-    if (!this.isModalOpen() && event.state && event.state[this.modalName]) {
-      history.go(-1)
+    if (!this.isModalOpen() && event.state?.[this.modalName]) {
+      // eslint-disable-next-line no-restricted-globals
+      history.back()
     }
   }
 
@@ -597,7 +610,8 @@ module.exports = class Dashboard extends UIPlugin {
     }
 
     // 1. Add a small (+) icon on drop
-    // (and prevent browsers from interpreting this as files being _moved_ into the browser, https://github.com/transloadit/uppy/issues/1978)
+    // (and prevent browsers from interpreting this as files being _moved_ into the
+    // browser, https://github.com/transloadit/uppy/issues/1978).
     event.dataTransfer.dropEffect = 'copy'
 
     clearTimeout(this.removeDragOverClassTimeout)
@@ -613,7 +627,8 @@ module.exports = class Dashboard extends UIPlugin {
     }
 
     clearTimeout(this.removeDragOverClassTimeout)
-    // Timeout against flickering, this solution is taken from drag-drop library. Solution with 'pointer-events: none' didn't work across browsers.
+    // Timeout against flickering, this solution is taken from drag-drop library.
+    // Solution with 'pointer-events: none' didn't work across browsers.
     this.removeDragOverClassTimeout = setTimeout(() => {
       this.setPluginState({ isDraggingOver: false })
     }, 50)
@@ -668,7 +683,8 @@ module.exports = class Dashboard extends UIPlugin {
   }
 
   /**
-   * We cancel thumbnail requests when a file item component unmounts to avoid clogging up the queue when the user scrolls past many elements.
+   * We cancel thumbnail requests when a file item component unmounts to avoid
+   * clogging up the queue when the user scrolls past many elements.
    */
   handleCancelThumbnail = (file) => {
     if (!this.opts.waitForThumbnailsBeforeUpload) {
@@ -681,11 +697,14 @@ module.exports = class Dashboard extends UIPlugin {
     if (event.keyCode === TAB_KEY) trapFocus.forInline(event, this.getPluginState().activeOverlayType, this.el)
   }
 
-  // ___Why do we listen to the 'paste' event on a document instead of onPaste={props.handlePaste} prop, or this.el.addEventListener('paste')?
+  // ___Why do we listen to the 'paste' event on a document instead of onPaste={props.handlePaste} prop,
+  //    or this.el.addEventListener('paste')?
   //    Because (at least) Chrome doesn't handle paste if focus is on some button, e.g. 'My Device'.
-  //    => Therefore, the best option is to listen to all 'paste' events, and only react to them when we are focused on our particular Uppy instance.
+  //    => Therefore, the best option is to listen to all 'paste' events, and only react to them when we are focused on our
+  //       particular Uppy instance.
   // ___Why do we still need onPaste={props.handlePaste} for the DashboardUi?
-  //    Because if we click on the 'Drop files here' caption e.g., `document.activeElement` will be 'body'. Which means our standard determination of whether we're pasting into our Uppy instance won't work.
+  //    Because if we click on the 'Drop files here' caption e.g., `document.activeElement` will be 'body'. Which means our
+  //    standard determination of whether we're pasting into our Uppy instance won't work.
   //    => Therefore, we need a traditional onPaste={props.handlePaste} handler too.
   handlePasteOnBody = (event) => {
     const isFocusInOverlay = this.el.contains(document.activeElement)
@@ -742,7 +761,7 @@ module.exports = class Dashboard extends UIPlugin {
     }
 
     if (this.opts.autoOpenFileEditor) {
-      this.uppy.on('files-added', this._openFileEditorWhenFilesAdded)
+      this.uppy.on('files-added', this.#openFileEditorWhenFilesAdded)
     }
   }
 
@@ -770,7 +789,7 @@ module.exports = class Dashboard extends UIPlugin {
     }
 
     if (this.opts.autoOpenFileEditor) {
-      this.uppy.off('files-added', this._openFileEditorWhenFilesAdded)
+      this.uppy.off('files-added', this.#openFileEditorWhenFilesAdded)
     }
   }
 
@@ -785,14 +804,20 @@ module.exports = class Dashboard extends UIPlugin {
       // If update is connected to showing the Informer - let the screen reader calmly read it.
       isInformerHidden
       && (
-        // If we are in a modal - always superfocus without concern for other elements on the page (user is unlikely to want to interact with the rest of the page)
+        // If we are in a modal - always superfocus without concern for other elements
+        // on the page (user is unlikely to want to interact with the rest of the page)
         isModal
         // If we are already inside of Uppy, or
         || isFocusInUppy
         // If we are not focused on anything BUT we have already, at least once, focused on uppy
-        //   1. We focus when isFocusNowhere, because when the element we were focused on disappears (e.g. an overlay), - focus gets lost. If user is typing something somewhere else on the page, - focus won't be 'nowhere'.
-        //   2. We only focus when focus is nowhere AND this.ifFocusedOnUppyRecently, to avoid focus jumps if we do something else on the page.
-        //   [Practical check] Without '&& this.ifFocusedOnUppyRecently', in Safari, in inline mode, when file is uploading, - navigate via tab to the checkbox, try to press space multiple times. Focus will jump to Uppy.
+        //   1. We focus when isFocusNowhere, because when the element we were focused
+        //      on disappears (e.g. an overlay), - focus gets lost. If user is typing
+        //      something somewhere else on the page, - focus won't be 'nowhere'.
+        //   2. We only focus when focus is nowhere AND this.ifFocusedOnUppyRecently,
+        //      to avoid focus jumps if we do something else on the page.
+        //   [Practical check] Without '&& this.ifFocusedOnUppyRecently', in Safari, in inline mode,
+        //                     when file is uploading, - navigate via tab to the checkbox,
+        //                     try to press space multiple times. Focus will jump to Uppy.
         || (isFocusNowhere && this.ifFocusedOnUppyRecently)
       )
     ) {
@@ -820,7 +845,7 @@ module.exports = class Dashboard extends UIPlugin {
     this.toggleFileCard(false, fileID)
   }
 
-  _attachRenderFunctionToTarget = (target) => {
+  #attachRenderFunctionToTarget = (target) => {
     const plugin = this.uppy.getPlugin(target.id)
     return {
       ...target,
@@ -829,7 +854,7 @@ module.exports = class Dashboard extends UIPlugin {
     }
   }
 
-  _isTargetSupported = (target) => {
+  #isTargetSupported = (target) => {
     const plugin = this.uppy.getPlugin(target.id)
     // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
     if (typeof plugin.isSupported !== 'function') {
@@ -838,22 +863,22 @@ module.exports = class Dashboard extends UIPlugin {
     return plugin.isSupported()
   }
 
-  _getAcquirers = memoize((targets) => {
+  #getAcquirers = memoize((targets) => {
     return targets
-      .filter(target => target.type === 'acquirer' && this._isTargetSupported(target))
-      .map(this._attachRenderFunctionToTarget)
+      .filter(target => target.type === 'acquirer' && this.#isTargetSupported(target))
+      .map(this.#attachRenderFunctionToTarget)
   })
 
-  _getProgressIndicators = memoize((targets) => {
+  #getProgressIndicators = memoize((targets) => {
     return targets
       .filter(target => target.type === 'progressindicator')
-      .map(this._attachRenderFunctionToTarget)
+      .map(this.#attachRenderFunctionToTarget)
   })
 
-  _getEditors = memoize((targets) => {
+  #getEditors = memoize((targets) => {
     return targets
       .filter(target => target.type === 'editor')
-      .map(this._attachRenderFunctionToTarget)
+      .map(this.#attachRenderFunctionToTarget)
   })
 
   render = (state) => {
@@ -874,9 +899,9 @@ module.exports = class Dashboard extends UIPlugin {
       isAllPaused,
     } = this.uppy.getObjectOfFilesPerState()
 
-    const acquirers = this._getAcquirers(pluginState.targets)
-    const progressindicators = this._getProgressIndicators(pluginState.targets)
-    const editors = this._getEditors(pluginState.targets)
+    const acquirers = this.#getAcquirers(pluginState.targets)
+    const progressindicators = this.#getProgressIndicators(pluginState.targets)
+    const editors = this.#getEditors(pluginState.targets)
 
     let theme
     if (this.opts.theme === 'auto') {
@@ -887,7 +912,8 @@ module.exports = class Dashboard extends UIPlugin {
 
     if (['files', 'folders', 'both'].indexOf(this.opts.fileManagerSelectionType) < 0) {
       this.opts.fileManagerSelectionType = 'files'
-      console.error(`Unsupported option for "fileManagerSelectionType". Using default of "${this.opts.fileManagerSelectionType}".`)
+      // eslint-disable-next-line no-console
+      console.warn(`Unsupported option for "fileManagerSelectionType". Using default of "${this.opts.fileManagerSelectionType}".`)
     }
 
     return DashboardUI({

+ 1 - 0
packages/@uppy/dashboard/src/utils/copyToClipboard.js

@@ -32,6 +32,7 @@ module.exports = function copyToClipboard (textToCopy, fallbackString) {
 
     const magicCopyFailed = () => {
       document.body.removeChild(textArea)
+      // eslint-disable-next-line no-alert
       window.prompt(fallbackString, textToCopy)
       resolve()
     }

+ 12 - 5
packages/@uppy/dashboard/src/utils/createSuperFocus.js

@@ -5,8 +5,12 @@ const getActiveOverlayEl = require('./getActiveOverlayEl')
 /*
   Focuses on some element in the currently topmost overlay.
 
-  1. If there are some [data-uppy-super-focusable] elements rendered already - focuses on the first superfocusable element, and leaves focus up to the control of a user (until currently focused element disappears from the screen [which can happen when overlay changes, or, e.g., when we click on a folder in googledrive]).
-  2. If there are no [data-uppy-super-focusable] elements yet (or ever) - focuses on the first focusable element, but switches focus if superfocusable elements appear on next render.
+  1. If there are some [data-uppy-super-focusable] elements rendered already - focuses
+     on the first superfocusable element, and leaves focus up to the control of
+     a user (until currently focused element disappears from the screen [which
+     can happen when overlay changes, or, e.g., when we click on a folder in googledrive]).
+  2. If there are no [data-uppy-super-focusable] elements yet (or ever) - focuses
+     on the first focusable element, but switches focus if superfocusable elements appear on next render.
 */
 module.exports = function createSuperFocus () {
   let lastFocusWasOnSuperFocusableEl = false
@@ -15,13 +19,15 @@ module.exports = function createSuperFocus () {
     const overlayEl = getActiveOverlayEl(dashboardEl, activeOverlayType)
 
     const isFocusInOverlay = overlayEl.contains(document.activeElement)
-    // If focus is already in the topmost overlay, AND on last update we focused on the superfocusable element - then leave focus up to the user.
+    // If focus is already in the topmost overlay, AND on last update we focused on the superfocusable
+    // element - then leave focus up to the user.
     // [Practical check] without this line, typing in the search input in googledrive overlay won't work.
     if (isFocusInOverlay && lastFocusWasOnSuperFocusableEl) return
 
     const superFocusableEl = overlayEl.querySelector('[data-uppy-super-focusable]')
     // If we are already in the topmost overlay, AND there are no super focusable elements yet, - leave focus up to the user.
-    // [Practical check] without this line, if you are in an empty folder in google drive, and something's uploading in the bg, - focus will be jumping to Done all the time.
+    // [Practical check] without this line, if you are in an empty folder in google drive, and something's uploading in the
+    // bg, - focus will be jumping to Done all the time.
     if (isFocusInOverlay && !superFocusableEl) return
 
     if (superFocusableEl) {
@@ -35,7 +41,8 @@ module.exports = function createSuperFocus () {
   }
 
   // ___Why do we need to debounce?
-  //    1. To deal with animations: overlay changes via animations, which results in the DOM updating AFTER plugin.update() already executed.
+  //    1. To deal with animations: overlay changes via animations, which results in the DOM updating AFTER plugin.update()
+  //       already executed.
   //    [Practical check] without debounce, if we open the Url overlay, and click 'Done', Dashboard won't get focused again.
   //    [Practical check] if we delay 250ms instead of 260ms - IE11 won't get focused in same situation.
   //    2. Performance: there can be many state update()s in a second, and this function is called every time.

+ 11 - 5
packages/@uppy/dashboard/src/utils/trapFocus.js

@@ -19,8 +19,11 @@ function focusOnLastNode (event, nodes) {
 }
 
 // ___Why not just use (focusedItemIndex === -1)?
-//    Firefox thinks <ul> is focusable, but we don't have <ul>s in our FOCUSABLE_ELEMENTS. Which means that if we tab into the <ul>, code will think that we are not in the active overlay, and we should focusOnFirstNode() of the currently active overlay!
-//    [Practical check] if we use (focusedItemIndex === -1), instagram provider in firefox will never get focus on its pics in the <ul>.
+//    Firefox thinks <ul> is focusable, but we don't have <ul>s in our FOCUSABLE_ELEMENTS. Which means that if we tab into
+//    the <ul>, code will think that we are not in the active overlay, and we should focusOnFirstNode() of the currently
+//    active overlay!
+//    [Practical check] if we use (focusedItemIndex === -1), instagram provider in firefox will never get focus on its pics
+//    in the <ul>.
 function isFocusInOverlay (activeOverlayEl) {
   return activeOverlayEl.contains(document.activeElement)
 }
@@ -31,8 +34,10 @@ function trapFocus (event, activeOverlayType, dashboardEl) {
 
   const focusedItemIndex = focusableNodes.indexOf(document.activeElement)
 
-  // If we pressed tab, and focus is not yet within the current overlay - focus on the first element within the current overlay.
-  // This is a safety measure (for when user returns from another tab e.g.), most plugins will try to focus on some important element as it loads.
+  // If we pressed tab, and focus is not yet within the current overlay - focus on
+  // the first element within the current overlay.
+  // This is a safety measure (for when user returns from another tab e.g.), most
+  // plugins will try to focus on some important element as it loads.
   if (!isFocusInOverlay(activeOverlayEl)) {
     focusOnFirstNode(event, focusableNodes)
   // If we pressed shift + tab, and we're on the first element of a modal
@@ -45,7 +50,8 @@ function trapFocus (event, activeOverlayType, dashboardEl) {
 }
 
 module.exports = {
-  // Traps focus inside of the currently open overlay (e.g. Dashboard, or e.g. Instagram), never lets focus disappear from the modal.
+  // Traps focus inside of the currently open overlay (e.g. Dashboard, or e.g. Instagram),
+  // never lets focus disappear from the modal.
   forModal: (event, activeOverlayType, dashboardEl) => {
     trapFocus(event, activeOverlayType, dashboardEl)
   },

+ 0 - 5
packages/@uppy/golden-retriever/src/IndexedDBStore.js

@@ -211,11 +211,6 @@ class IndexedDBStore {
           const cursor = event.target.result
           if (cursor) {
             const entry = cursor.value
-            console.log(
-              '[IndexedDBStore] Deleting record', entry.fileID,
-              'of size', prettierBytes(entry.data.size),
-              '- expired on', new Date(entry.expires)
-            )
             cursor.delete() // Ignoring return value … it's not terrible if this goes wrong.
             cursor.continue()
           } else {

+ 1 - 4
packages/@uppy/golden-retriever/src/ServiceWorker.js

@@ -1,4 +1,5 @@
 /* globals clients */
+/* eslint-disable no-restricted-globals */
 
 const fileCache = Object.create(null)
 
@@ -10,8 +11,6 @@ function getCache (name) {
 }
 
 self.addEventListener('install', (event) => {
-  console.log('Installing Uppy Service Worker...')
-
   event.waitUntil(Promise.resolve()
     .then(() => self.skipWaiting()))
 })
@@ -30,12 +29,10 @@ function sendMessageToAllClients (msg) {
 
 function addFile (store, file) {
   getCache(store)[file.id] = file.data
-  console.log('Added file blob to service worker cache:', file.data)
 }
 
 function removeFile (store, fileID) {
   delete getCache(store)[fileID]
-  console.log('Removed file blob from service worker cache:', fileID)
 }
 
 function getFiles (store) {

+ 1 - 1
packages/@uppy/informer/src/TransitionGroup.js

@@ -1,7 +1,7 @@
+/* eslint-disable */
 /**
  * @source https://github.com/developit/preact-transition-group
  */
-/* eslint-disable */
 'use strict'
 
 const { Component, cloneElement, h, toChildArray } = require('preact')

+ 1 - 1
packages/@uppy/locales/src/ar_SA.js

@@ -137,7 +137,7 @@ ar_SA.strings = {
   openFolderNamed: 'افتح المجلد %{name}',
 }
 
-ar_SA.pluralize = function (n) {
+ar_SA.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/bg_BG.js

@@ -143,7 +143,7 @@ bg_BG.strings = {
   },
 }
 
-bg_BG.pluralize = function (count) {
+bg_BG.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/cs_CZ.js

@@ -138,7 +138,7 @@ cs_CZ.strings = {
   },
 }
 
-cs_CZ.pluralize = function (n) {
+cs_CZ.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/da_DK.js

@@ -137,7 +137,7 @@ da_DK.strings = {
   },
 }
 
-da_DK.pluralize = function (n) {
+da_DK.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/de_DE.js

@@ -177,7 +177,7 @@ de_DE.strings = {
   zoomOut: 'Verkleinern',
 }
 
-de_DE.pluralize = function (count) {
+de_DE.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/el_GR.js

@@ -137,7 +137,7 @@ el_GR.strings = {
   },
 }
 
-el_GR.pluralize = function (n) {
+el_GR.pluralize = function pluralize (n) {
   if (n === 1) {
     return 1
   }

+ 1 - 1
packages/@uppy/locales/src/en_US.js

@@ -178,7 +178,7 @@ en_US.strings = {
   zoomOut: 'Zoom out',
 }
 
-en_US.pluralize = function (count) {
+en_US.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/es_ES.js

@@ -138,7 +138,7 @@ es_ES.strings = {
   openFolderNamed: 'Carpeta abierta %{name}',
 }
 
-es_ES.pluralize = function (n) {
+es_ES.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/fa_IR.js

@@ -171,7 +171,7 @@ fa_IR.strings = {
 
 }
 
-fa_IR.pluralize = function (n) {
+fa_IR.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/fi_FI.js

@@ -144,7 +144,7 @@ fi_FI.strings = {
   recording: 'Tallennetaan',
 }
 
-fi_FI.pluralize = function (n) {
+fi_FI.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/fr_FR.js

@@ -154,7 +154,7 @@ fr_FR.strings = {
   },
 }
 
-fr_FR.pluralize = function (n) {
+fr_FR.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/gl_ES.js

@@ -137,7 +137,7 @@ gl_ES.strings = {
   openFolderNamed: 'Cartafol aberto %{name}',
 }
 
-gl_ES.pluralize = function (n) {
+gl_ES.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/he_IL.js

@@ -139,7 +139,7 @@ he_IL.strings = {
   },
 }
 
-he_IL.pluralize = function (n) {
+he_IL.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/hr_HR.js

@@ -137,7 +137,7 @@ hr_HR.strings = {
   openFolderNamed: 'Otvori mapu %{name}',
 }
 
-hr_HR.pluralize = function (n) {
+hr_HR.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/hu_HU.js

@@ -139,7 +139,7 @@ hu_HU.strings = {
   openFolderNamed: 'Nyitott mappa %{name}',
 }
 
-hu_HU.pluralize = function (n) {
+hu_HU.pluralize = function pluralize (n) {
   return 0
 }
 

+ 1 - 1
packages/@uppy/locales/src/id_ID.js

@@ -137,7 +137,7 @@ id_ID.strings = {
   },
 }
 
-id_ID.pluralize = function (n) {
+id_ID.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/is_IS.js

@@ -144,7 +144,7 @@ is_IS.strings = {
   },
 }
 
-is_IS.pluralize = function (n) {
+is_IS.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/it_IT.js

@@ -137,7 +137,7 @@ it_IT.strings = {
   openFolderNamed: 'Cartella aperta %{name}',
 }
 
-it_IT.pluralize = function (n) {
+it_IT.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/ja_JP.js

@@ -137,7 +137,7 @@ ja_JP.strings = {
   openFolderNamed: '開いたフォルダ %{name}',
 }
 
-ja_JP.pluralize = function (n) {
+ja_JP.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/ko_KR.js

@@ -96,7 +96,7 @@ ko_KR.strings = {
   youHaveToAtLeastSelectX: '최소 %{smart_count}개의 파일을 선택해야 합니다',
 }
 
-ko_KR.pluralize = function (n) {
+ko_KR.pluralize = function pluralize (n) {
   return 0
 }
 

+ 1 - 1
packages/@uppy/locales/src/nb_NO.js

@@ -158,7 +158,7 @@ nb_NO.strings = {
   zoomOut: 'Zoom ut',
 }
 
-nb_NO.pluralize = function (count) {
+nb_NO.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/nl_NL.js

@@ -130,7 +130,7 @@ nl_NL.strings = {
   openFolderNamed: 'Open map %{name}',
 }
 
-nl_NL.pluralize = function (n) {
+nl_NL.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/pl_PL.js

@@ -146,7 +146,7 @@ pl_PL.strings = {
   },
 }
 
-pl_PL.pluralize = function (n) {
+pl_PL.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/pt_BR.js

@@ -131,7 +131,7 @@ pt_BR.strings = {
   openFolderNamed: 'Pasta aberta %{name}',
 }
 
-pt_BR.pluralize = function (n) {
+pt_BR.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/pt_PT.js

@@ -131,7 +131,7 @@ pt_PT.strings = {
   openFolderNamed: 'Pasta aberta %{name}',
 }
 
-pt_PT.pluralize = function (n) {
+pt_PT.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/ro_RO.js

@@ -136,7 +136,7 @@ ro_RO.strings = {
   },
 }
 
-ro_RO.pluralize = function (count) {
+ro_RO.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/ru_RU.js

@@ -139,7 +139,7 @@ ru_RU.strings = {
   openFolderNamed: 'Открыть папку %{name}',
 }
 
-ru_RU.pluralize = function (n) {
+ru_RU.pluralize = function pluralize (n) {
   if (n % 10 === 1 && n % 100 !== 11) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/sk_SK.js

@@ -167,7 +167,7 @@ sk_SK.strings = {
   zoomOut: 'Oddialiť',
 }
 
-sk_SK.pluralize = function (count) {
+sk_SK.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/sr_RS_Cyrillic.js

@@ -137,7 +137,7 @@ sr_RS_Cyrillic.strings = {
   openFolderNamed: 'Отвори фолдер %{name}',
 }
 
-sr_RS_Cyrillic.pluralize = function (n) {
+sr_RS_Cyrillic.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/sr_RS_Latin.js

@@ -137,7 +137,7 @@ sr_RS_Latin.strings = {
   openFolderNamed: 'Otvori folder %{name}',
 }
 
-sr_RS_Latin.pluralize = function (n) {
+sr_RS_Latin.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/sv_SE.js

@@ -137,7 +137,7 @@ sv_SE.strings = {
   },
 }
 
-sv_SE.pluralize = function (n) {
+sv_SE.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/th_TH.js

@@ -156,7 +156,7 @@ th_TH.strings = {
   zoomOut: 'ซูมออก',
 }
 
-th_TH.pluralize = function (n) {
+th_TH.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/tr_TR.js

@@ -137,7 +137,7 @@ tr_TR.strings = {
   openFolderNamed: 'Açık dosya %{name}',
 }
 
-tr_TR.pluralize = function (n) {
+tr_TR.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/uk_UA.js

@@ -137,7 +137,7 @@ uk_UA.strings = {
   openFolderNamed: 'Відкрити теку %{name}',
 }
 
-uk_UA.pluralize = function (n) {
+uk_UA.pluralize = function pluralize (n) {
   if (n % 10 === 1 && n % 100 !== 11) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/vi_VN.js

@@ -140,7 +140,7 @@ vi_VN.strings = {
   },
 }
 
-vi_VN.pluralize = function (n) {
+vi_VN.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/src/zh_CN.js

@@ -110,7 +110,7 @@ zh_CN.strings = {
 }
 
 // There is just one form.
-zh_CN.pluralize = function (n) { return 0 }
+zh_CN.pluralize = function pluralize (n) { return 0 }
 
 if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
   window.Uppy.locales.zh_CN = zh_CN

+ 1 - 1
packages/@uppy/locales/src/zh_TW.js

@@ -146,7 +146,7 @@ zh_TW.strings = {
   },
 }
 
-zh_TW.pluralize = function (n) {
+zh_TW.pluralize = function pluralize (n) {
   if (n === 1) {
     return 0
   }

+ 1 - 1
packages/@uppy/locales/template.js

@@ -2,7 +2,7 @@ const en_US = {}
 
 en_US.strings = {}
 
-en_US.pluralize = function (count) {
+en_US.pluralize = function pluralize (count) {
   if (count === 1) {
     return 0
   }

+ 2 - 2
packages/@uppy/provider-views/src/FooterActions.js

@@ -3,12 +3,12 @@ const { h } = require('preact')
 module.exports = (props) => {
   return (
     <div className="uppy-ProviderBrowser-footer">
-      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-primary" onClick={props.done}>
+      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-primary" onClick={props.done} type="button">
         {props.i18n('selectX', {
           smart_count: props.selected,
         })}
       </button>
-      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-link" onClick={props.cancel}>
+      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-link" onClick={props.cancel} type="button">
         {props.i18n('cancel')}
       </button>
     </div>

+ 1 - 1
packages/@uppy/provider-views/src/Item/components/ItemIcon.js

@@ -36,6 +36,6 @@ module.exports = (props) => {
     case 'video':
       return <VideoIcon />
     default:
-      return <img src={props.itemIconString} />
+      return <img src={props.itemIconString} alt={props.alt} />
   }
 }

+ 23 - 25
packages/@uppy/provider-views/src/ProviderView/AuthView.js

@@ -1,30 +1,28 @@
-const { h, Component } = require('preact')
+const { h } = require('preact')
 
-class AuthView extends Component {
-  render () {
-    const pluginNameComponent = (
-      <span className="uppy-Provider-authTitleName">
-        {this.props.pluginName}
-        <br />
-      </span>
-    )
-    return (
-      <div className="uppy-Provider-auth">
-        <div className="uppy-Provider-authIcon">{this.props.pluginIcon()}</div>
-        <div className="uppy-Provider-authTitle">
-          {this.props.i18nArray('authenticateWithTitle', { pluginName: pluginNameComponent })}
-        </div>
-        <button
-          type="button"
-          className="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Provider-authBtn"
-          onClick={this.props.handleAuth}
-          data-uppy-super-focusable
-        >
-          {this.props.i18nArray('authenticateWith', { pluginName: this.props.pluginName })}
-        </button>
+function AuthView (props) {
+  const pluginNameComponent = (
+    <span className="uppy-Provider-authTitleName">
+      {props.pluginName}
+      <br />
+    </span>
+  )
+  return (
+    <div className="uppy-Provider-auth">
+      <div className="uppy-Provider-authIcon">{props.pluginIcon()}</div>
+      <div className="uppy-Provider-authTitle">
+        {props.i18nArray('authenticateWithTitle', { pluginName: pluginNameComponent })}
       </div>
-    )
-  }
+      <button
+        type="button"
+        className="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Provider-authBtn"
+        onClick={props.handleAuth}
+        data-uppy-super-focusable
+      >
+        {props.i18nArray('authenticateWith', { pluginName: props.pluginName })}
+      </button>
+    </div>
+  )
 }
 
 module.exports = AuthView

+ 19 - 14
packages/@uppy/provider-views/src/ProviderView/ProviderView.js

@@ -11,6 +11,7 @@ const SharedHandler = require('../SharedHandler')
 const CloseWrapper = require('../CloseWrapper')
 
 function getOrigin () {
+  // eslint-disable-next-line no-restricted-globals
   return location.origin
 }
 
@@ -20,6 +21,10 @@ function getOrigin () {
 module.exports = class ProviderView {
   static VERSION = require('../../package.json').version
 
+  #isHandlingScroll
+
+  #sharedHandler
+
   /**
    * @param {object} plugin instance of the plugin
    * @param {object} opts
@@ -27,7 +32,7 @@ module.exports = class ProviderView {
   constructor (plugin, opts) {
     this.plugin = plugin
     this.provider = opts.provider
-    this._sharedHandler = new SharedHandler(plugin)
+    this.#sharedHandler = new SharedHandler(plugin)
 
     // set default options
     const defaultOptions = {
@@ -75,7 +80,7 @@ module.exports = class ProviderView {
     // Nothing.
   }
 
-  _updateFilesAndFolders (res, files, folders) {
+  #updateFilesAndFolders (res, files, folders) {
     this.nextPagePath = res.nextPagePath
     res.items.forEach((item) => {
       if (item.isFolder) {
@@ -104,7 +109,7 @@ module.exports = class ProviderView {
    * @returns {Promise}   Folders/files in folder
    */
   getFolder (id, name) {
-    return this._sharedHandler.loaderWrapper(
+    return this.#sharedHandler.loaderWrapper(
       this.provider.list(id),
       (res) => {
         const folders = []
@@ -121,7 +126,7 @@ module.exports = class ProviderView {
         }
 
         this.username = res.username || this.username
-        this._updateFilesAndFolders(res, files, folders)
+        this.#updateFilesAndFolders(res, files, folders)
         this.plugin.setPluginState({ directories: updatedDirectories })
       },
       this.handleError
@@ -292,7 +297,7 @@ module.exports = class ProviderView {
 
     const authWindow = window.open(link, '_blank')
     const handleToken = (e) => {
-      if (!this._isOriginAllowed(e.origin, this.plugin.opts.companionAllowedHosts) || e.source !== authWindow) {
+      if (!this.#isOriginAllowed(e.origin, this.plugin.opts.companionAllowedHosts) || e.source !== authWindow) {
         this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.companionAllowedHosts}`)
         return
       }
@@ -314,7 +319,7 @@ module.exports = class ProviderView {
     window.addEventListener('message', handleToken)
   }
 
-  _isOriginAllowed (origin, allowedOrigin) {
+  #isOriginAllowed (origin, allowedOrigin) {
     const getRegex = (value) => {
       if (typeof value === 'string') {
         return new RegExp(`^${value}$`)
@@ -343,15 +348,15 @@ module.exports = class ProviderView {
     const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
     const path = this.nextPagePath || null
 
-    if (scrollPos < 50 && path && !this._isHandlingScroll) {
+    if (scrollPos < 50 && path && !this.#isHandlingScroll) {
       this.provider.list(path)
         .then((res) => {
           const { files, folders } = this.plugin.getPluginState()
-          this._updateFilesAndFolders(res, files, folders)
+          this.#updateFilesAndFolders(res, files, folders)
         }).catch(this.handleError)
-        .then(() => { this._isHandlingScroll = false }) // always called
+        .then(() => { this.#isHandlingScroll = false }) // always called
 
-      this._isHandlingScroll = true
+      this.#isHandlingScroll = true
     }
   }
 
@@ -386,7 +391,7 @@ module.exports = class ProviderView {
       return this.addFile(file)
     })
 
-    this._sharedHandler.loaderWrapper(Promise.all(promises), () => {
+    this.#sharedHandler.loaderWrapper(Promise.all(promises), () => {
       this.clearSelection()
     }, () => {})
   }
@@ -449,11 +454,11 @@ module.exports = class ProviderView {
       username: this.username,
       getNextFolder: this.getNextFolder,
       getFolder: this.getFolder,
-      filterItems: this._sharedHandler.filterItems,
+      filterItems: this.#sharedHandler.filterItems,
       filterQuery: this.filterQuery,
       logout: this.logout,
-      isChecked: this._sharedHandler.isChecked,
-      toggleCheckbox: this._sharedHandler.toggleCheckbox,
+      isChecked: this.#sharedHandler.isChecked,
+      toggleCheckbox: this.#sharedHandler.toggleCheckbox,
       handleScroll: this.handleScroll,
       listAllFiles: this.listAllFiles,
       done: this.donePicking,

+ 20 - 14
packages/@uppy/provider-views/src/SearchProviderView/SearchProviderView.js

@@ -15,6 +15,12 @@ const CloseWrapper = require('../CloseWrapper')
 module.exports = class ProviderView {
   static VERSION = require('../../package.json').version
 
+  #isHandlingScroll
+
+  #searchTerm
+
+  #sharedHandler
+
   /**
    * @param {object} plugin instance of the plugin
    * @param {object} opts
@@ -22,7 +28,7 @@ module.exports = class ProviderView {
   constructor (plugin, opts) {
     this.plugin = plugin
     this.provider = opts.provider
-    this._sharedHandler = new SharedHandler(plugin)
+    this.#sharedHandler = new SharedHandler(plugin)
 
     // set default options
     const defaultOptions = {
@@ -66,9 +72,9 @@ module.exports = class ProviderView {
     // Nothing.
   }
 
-  _updateFilesAndInputMode (res, files) {
+  #updateFilesAndInputMode (res, files) {
     this.nextPageQuery = res.nextPageQuery
-    this._searchTerm = res.searchedFor
+    this.#searchTerm = res.searchedFor
     res.items.forEach((item) => { files.push(item) })
     this.plugin.setPluginState({ isInputMode: false, files })
   }
@@ -83,16 +89,16 @@ module.exports = class ProviderView {
   }
 
   search (query) {
-    if (query && query === this._searchTerm) {
+    if (query && query === this.#searchTerm) {
       // no need to search again as this is the same as the previous search
       this.plugin.setPluginState({ isInputMode: false })
       return
     }
 
-    return this._sharedHandler.loaderWrapper(
+    return this.#sharedHandler.loaderWrapper(
       this.provider.search(query),
       (res) => {
-        this._updateFilesAndInputMode(res, [])
+        this.#updateFilesAndInputMode(res, [])
       },
       this.handleError
     )
@@ -159,15 +165,15 @@ module.exports = class ProviderView {
     const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
     const query = this.nextPageQuery || null
 
-    if (scrollPos < 50 && query && !this._isHandlingScroll) {
-      this.provider.search(this._searchTerm, query)
+    if (scrollPos < 50 && query && !this.#isHandlingScroll) {
+      this.provider.search(this.#searchTerm, query)
         .then((res) => {
           const { files } = this.plugin.getPluginState()
-          this._updateFilesAndInputMode(res, files)
+          this.#updateFilesAndInputMode(res, files)
         }).catch(this.handleError)
-        .then(() => { this._isHandlingScroll = false }) // always called
+        .then(() => { this.#isHandlingScroll = false }) // always called
 
-      this._isHandlingScroll = true
+      this.#isHandlingScroll = true
     }
   }
 
@@ -175,7 +181,7 @@ module.exports = class ProviderView {
     const { currentSelection } = this.plugin.getPluginState()
     const promises = currentSelection.map((file) => this.addFile(file))
 
-    this._sharedHandler.loaderWrapper(Promise.all(promises), () => {
+    this.#sharedHandler.loaderWrapper(Promise.all(promises), () => {
       this.clearSelection()
     }, () => {})
   }
@@ -221,8 +227,8 @@ module.exports = class ProviderView {
     const targetViewOptions = { ...this.opts, ...viewOptions }
     const browserProps = {
       ...this.plugin.getPluginState(),
-      isChecked: this._sharedHandler.isChecked,
-      toggleCheckbox: this._sharedHandler.toggleCheckbox,
+      isChecked: this.#sharedHandler.isChecked,
+      toggleCheckbox: this.#sharedHandler.toggleCheckbox,
       handleScroll: this.handleScroll,
       done: this.donePicking,
       cancel: this.cancelPicking,

+ 5 - 5
packages/@uppy/react-native/file-picker/index.js

@@ -46,6 +46,7 @@ export default class UppyReactNativeFilePicker extends React.Component {
       })
       this.props.onRequestClose()
     }).catch((err) => {
+      // eslint-disable-next-line no-console
       console.log(err)
     })
   }
@@ -60,6 +61,7 @@ export default class UppyReactNativeFilePicker extends React.Component {
       })
       this.props.onRequestClose()
     }).catch((err) => {
+      // eslint-disable-next-line no-console
       console.log(err)
     })
   }
@@ -73,20 +75,18 @@ export default class UppyReactNativeFilePicker extends React.Component {
       })
       this.props.onRequestClose()
     }).catch((err) => {
+      // eslint-disable-next-line no-console
       console.log(err)
     })
   }
 
   openProvider (id) {
-    console.log('Open provider:', id)
     this.setState({
       openProvider: id,
     })
   }
 
   chooseProvider (id) {
-    console.log('Provider selected:', id)
-
     switch (id) {
       case 'LocalImages':
         this.selectImage()
@@ -107,11 +107,11 @@ export default class UppyReactNativeFilePicker extends React.Component {
       <ScrollView
         contentContainerStyle={styles.providerList}
       >
-        {this.state.providers.map((item, index) => {
+        {this.state.providers.map((item) => {
           return (
             <TouchableOpacity
               style={styles.providerButton}
-              key={index}
+              key={item.title}
               onPress={ev => this.chooseProvider(item.id)}
             >
               <Text style={styles.providerButtonText}>{item.title}</Text>

+ 0 - 3
packages/@uppy/react-native/file-picker/instagram.js

@@ -78,7 +78,6 @@ export default class UppyRNInstagram extends React.Component {
   }
 
   renderInstagram () {
-    console.log(this.state.authUrl)
     return (
       <WebView
         source={{ uri: this.state.authUrl }}
@@ -86,9 +85,7 @@ export default class UppyRNInstagram extends React.Component {
         onNavigationStateChange={(ev) => {
           const { url } = ev
           const token = getQueryParamValueFromUrl('uppyAuthToken', url)
-          console.log(token)
           this.plugin.provider.setAuthToken(token)
-          console.log(this.plugin.provider.list('recent'))
           // return this.renderGrid(this.state.instagram.items)
         }}
       />

+ 1 - 0
packages/@uppy/react-native/file-picker/url.js

@@ -42,6 +42,7 @@ export default class UppyRNUrl extends React.Component {
     this.plugin.addFile(this.state.url)
       .then(this.props.onDone)
       .catch((err) => {
+        // eslint-disable-next-line no-console
         console.log(err)
       })
   }

+ 1 - 1
packages/@uppy/react/src/propTypes.js

@@ -9,7 +9,7 @@ const plugins = PropTypes.arrayOf(PropTypes.string)
 
 // Language strings for this component.
 const locale = PropTypes.shape({
-  strings: PropTypes.object,
+  strings: PropTypes.object, // eslint-disable-line react/forbid-prop-types
   pluralize: PropTypes.func,
 })
 

+ 3 - 0
packages/@uppy/redux-dev-tools/src/index.js

@@ -1,11 +1,13 @@
 const { UIPlugin } = require('@uppy/core')
 
+/* eslint-disable max-len */
 /**
  * Add Redux DevTools support to Uppy
  *
  * See https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f
  * and https://github.com/zalmoxisus/mobx-remotedev/blob/master/src/monitorActions.js
  */
+/* eslint-enable max-len */
 module.exports = class ReduxDevTools extends UIPlugin {
   static VERSION = require('../package.json').version
 
@@ -54,6 +56,7 @@ module.exports = class ReduxDevTools extends UIPlugin {
   }
 
   install () {
+    // eslint-disable-next-line no-underscore-dangle
     this.withDevTools = typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__
     if (this.withDevTools) {
       this.initDevTools()

+ 3 - 4
packages/@uppy/robodog/src/TransloaditResultsPlugin.js

@@ -9,17 +9,16 @@ class TransloaditResultsPlugin extends BasePlugin {
 
     this.type = 'modifier'
     this.id = this.opts.id || 'TransloaditResultsPlugin'
-    this._afterUpload = this._afterUpload.bind(this)
   }
 
   install () {
-    this.uppy.addPostProcessor(this._afterUpload)
+    this.uppy.addPostProcessor(this.#afterUpload)
   }
 
-  _afterUpload (fileIDs, uploadID) {
+  #afterUpload = (fileIDs, uploadID) => {
     const { currentUploads } = this.uppy.getState()
     const { result } = currentUploads[uploadID]
-    const assemblies = result && Array.isArray(result.transloadit) ? result.transloadit : []
+    const assemblies = Array.isArray(result?.transloadit) ? result.transloadit : []
 
     // Merge the assembly.results[*] arrays and add `stepName` and
     // `assemblyId` properties.

+ 2 - 0
packages/@uppy/robodog/types/index.test-d.ts

@@ -2,6 +2,8 @@ import { Transloadit } from 'uppy' // eslint-disable-line import/no-extraneous-d
 import { expectError } from 'tsd'
 import Robodog from '.'
 
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
 async function performPick () {
   const { successful, failed, transloadit, results } = await Robodog.pick({
     target: 'test',

+ 2 - 1
packages/@uppy/screen-capture/src/CaptureScreen.js

@@ -39,7 +39,8 @@ class RecorderScreen extends Component {
       <div className="uppy uppy-ScreenCapture-container">
         <div className="uppy-ScreenCapture-videoContainer">
           <StreamStatus {...this.props} />
-          <video ref={videoElement => (this.videoElement = videoElement)} className="uppy-ScreenCapture-video" {...videoProps} />
+          {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
+          <video ref={videoElement => { this.videoElement = videoElement }} className="uppy-ScreenCapture-video" {...videoProps} />
           <StopWatch {...this.props} />
         </div>
 

+ 1 - 0
packages/@uppy/screen-capture/src/StopWatch.js

@@ -64,6 +64,7 @@ class Stopwatch extends Component {
   }
 
   fmtMSS (s) {
+    // eslint-disable-next-line no-return-assign
     return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
   }
 

+ 5 - 2
packages/@uppy/screen-capture/src/index.js

@@ -20,7 +20,8 @@ module.exports = class ScreenCapture extends UIPlugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.mediaDevices = getMediaDevices()
-    this.protocol = location.protocol.match(/https/i) ? 'https' : 'http'
+    // eslint-disable-next-line no-restricted-globals
+    this.protocol = location.protocol === 'https:' ? 'https' : 'http'
     this.id = this.opts.id || 'ScreenCapture'
     this.title = this.opts.title || 'Screencast'
     this.type = 'acquirer'
@@ -209,7 +210,9 @@ module.exports = class ScreenCapture extends UIPlugin {
       .then((videoStream) => {
         // Attempt to use the passed preferredVideoMimeType (if any) during recording.
         // If the browser doesn't support it, we'll fall back to the browser default instead
-        if (preferredVideoMimeType && MediaRecorder.isTypeSupported(preferredVideoMimeType) && getFileTypeExtension(preferredVideoMimeType)) {
+        if (preferredVideoMimeType
+            && MediaRecorder.isTypeSupported(preferredVideoMimeType)
+            && getFileTypeExtension(preferredVideoMimeType)) {
           options.mimeType = preferredVideoMimeType
         }
 

+ 8 - 4
packages/@uppy/status-bar/src/StatusBar.js

@@ -134,6 +134,8 @@ module.exports = (props) => {
         className={progressClassNames}
         style={{ width: `${width}%` }}
         role="progressbar"
+        aria-label={`${width}%`}
+        aria-valuetext={`${width}%`}
         aria-valuemin="0"
         aria-valuemax="100"
         aria-valuenow={progressValue}
@@ -371,6 +373,7 @@ const ProgressBarUploading = (props) => {
         <div className="uppy-StatusBar-statusPrimary">
           {props.supportsUploadProgress ? `${title}: ${props.totalProgress}%` : title}
         </div>
+        {/* eslint-disable-next-line no-nested-ternary */}
         {!props.isAllPaused && !showUploadNewlyAddedFiles && props.showProgressDetails
           ? (props.supportsUploadProgress ? <ThrottledProgressDetails {...props} /> : <UnknownProgressDetails {...props} />)
           : null}
@@ -398,7 +401,8 @@ const ProgressBarComplete = ({ totalProgress, i18n }) => {
 const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
   function displayErrorAlert () {
     const errorMessage = `${i18n('uploadFailed')} \n\n ${error}`
-    alert(errorMessage)
+    // eslint-disable-next-line no-alert
+    alert(errorMessage) // TODO: move to custom alert implementation
   }
 
   return (
@@ -411,16 +415,16 @@ const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
           {i18n('uploadFailed')}
         </div>
       </div>
-      <span
+      <button
         className="uppy-StatusBar-details"
         aria-label={error}
         data-microtip-position="top-right"
         data-microtip-size="medium"
-        role="tooltip"
         onClick={displayErrorAlert}
+        type="button"
       >
         ?
-      </span>
+      </button>
     </div>
   )
 }

+ 3 - 1
packages/@uppy/status-bar/src/index.js

@@ -133,7 +133,9 @@ module.exports = class StatusBar extends UIPlugin {
       }
       // If NO files are being preprocessed or uploaded right now, but some files are
       // being postprocessed, show the postprocess state.
-      if (progress.postprocess && state !== statusBarStates.STATE_UPLOADING && state !== statusBarStates.STATE_PREPROCESSING) {
+      if (progress.postprocess
+          && state !== statusBarStates.STATE_UPLOADING
+          && state !== statusBarStates.STATE_PREPROCESSING) {
         state = statusBarStates.STATE_POSTPROCESSING
       }
     }

+ 1 - 0
packages/@uppy/status-bar/src/style.scss

@@ -385,6 +385,7 @@
 }
 
 .uppy-StatusBar-details {
+  appearance: none;
   line-height: 12px;
   width: 13px;
   height: 13px;

+ 2 - 2
packages/@uppy/store-default/src/index.js

@@ -18,7 +18,7 @@ class DefaultStore {
     const nextState = { ...this.state, ...patch }
 
     this.state = nextState
-    this._publish(prevState, nextState, patch)
+    this.#publish(prevState, nextState, patch)
   }
 
   subscribe (listener) {
@@ -32,7 +32,7 @@ class DefaultStore {
     }
   }
 
-  _publish (...args) {
+  #publish (...args) {
     this.callbacks.forEach((listener) => {
       listener(...args)
     })

+ 17 - 7
packages/@uppy/store-redux/src/index.js

@@ -18,10 +18,16 @@ const defaultSelector = (id) => (state) => state.uppy[id]
 class ReduxStore {
   static VERSION = require('../package.json').version
 
+  #id
+
+  #selector
+
+  #store
+
   constructor (opts) {
-    this._store = opts.store
-    this._id = opts.id || nanoid()
-    this._selector = opts.selector || defaultSelector(this._id)
+    this.#store = opts.store
+    this.#id = opts.id || nanoid()
+    this.#selector = opts.selector || defaultSelector(this.#id)
 
     // Calling `setState` to dispatch an action to the Redux store.
     // The intent is to make sure that the reducer has run once.
@@ -29,20 +35,20 @@ class ReduxStore {
   }
 
   setState (patch) {
-    this._store.dispatch({
+    this.#store.dispatch({
       type: STATE_UPDATE,
-      id: this._id,
+      id: this.#id,
       payload: patch,
     })
   }
 
   getState () {
-    return this._selector(this._store.getState())
+    return this.#selector(this.#store.getState())
   }
 
   subscribe (cb) {
     let prevState = this.getState()
-    return this._store.subscribe(() => {
+    return this.#store.subscribe(() => {
       const nextState = this.getState()
       if (prevState !== nextState) {
         const patch = getPatch(prevState, nextState)
@@ -51,6 +57,10 @@ class ReduxStore {
       }
     })
   }
+
+  [Symbol.for('uppy test: get id')] () {
+    return this.#id
+  }
 }
 
 function getPatch (prev, next) {

+ 1 - 1
packages/@uppy/store-redux/src/index.test.js

@@ -82,7 +82,7 @@ describe('ReduxStore', () => {
       type: 'SET',
       payload: {
         uppy: {
-          [store._id]: { b: 2 },
+          [store[Symbol.for('uppy test: get id')]()]: { b: 2 },
         },
       },
     })

+ 3 - 3
packages/@uppy/thumbnail-generator/src/index.js

@@ -160,8 +160,8 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
     if (steps < 1) {
       steps = 1
     }
-    let sW = targetWidth * Math.pow(2, steps - 1)
-    let sH = targetHeight * Math.pow(2, steps - 1)
+    let sW = targetWidth * 2 ** (steps - 1)
+    let sH = targetHeight * 2 ** (steps - 1)
     const x = 2
 
     while (steps--) {
@@ -260,7 +260,7 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
         return
       }
       return this.requestThumbnail(current)
-        .catch(err => {}) // eslint-disable-line handle-callback-err
+        .catch(() => {}) // eslint-disable-line node/handle-callback-err
         .then(() => this.processQueue())
     }
     this.queueProcessing = false

+ 2 - 1
packages/@uppy/transloadit/src/Assembly.js

@@ -12,7 +12,8 @@ const parseUrl = require('./parseUrl')
 let socketIo
 function requireSocketIo () {
   // eslint-disable-next-line global-require
-  return socketIo ??= require('socket.io-client')
+  socketIo ??= require('socket.io-client')
+  return socketIo
 }
 
 const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'

+ 1 - 1
packages/@uppy/tus/src/getFingerprint.js

@@ -23,7 +23,7 @@ function isReactNative () {
 // For React Native and Cordova, we let tus-js-client’s default
 // fingerprint handling take charge.
 module.exports = function getFingerprint (uppyFileObj) {
-  return function (file, options) {
+  return (file, options) => {
     if (isCordova() || isReactNative()) {
       return tus.defaultOptions.fingerprint(file, options)
     }

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio