diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index a4ad6bd3473b..000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "root": true, - "extends": [ - "plugin:import/errors", - "plugin:import/warnings", - "plugin:unicorn/recommended", - "xo", - "xo/browser" - ], - "rules": { - "arrow-body-style": "off", - "capitalized-comments": "off", - "comma-dangle": [ - "error", - "never" - ], - "import/extensions": [ - "error", - "ignorePackages", - { - "js": "always" - } - ], - "import/first": "error", - "import/newline-after-import": "error", - "import/no-absolute-path": "error", - "import/no-amd": "error", - "import/no-cycle": [ - "error", - { - "ignoreExternal": true - } - ], - "import/no-duplicates": "error", - "import/no-extraneous-dependencies": "error", - "import/no-mutable-exports": "error", - "import/no-named-as-default": "error", - "import/no-named-as-default-member": "error", - "import/no-named-default": "error", - "import/no-self-import": "error", - "import/no-unassigned-import": [ - "error" - ], - "import/no-useless-path-segments": "error", - "import/order": "error", - "indent": [ - "error", - 2, - { - "MemberExpression": "off", - "SwitchCase": 1 - } - ], - "logical-assignment-operators": "off", - "max-params": [ - "warn", - 5 - ], - "multiline-ternary": [ - "error", - "always-multiline" - ], - "new-cap": [ - "error", - { - "properties": false - } - ], - "no-console": "error", - "no-negated-condition": "off", - "object-curly-spacing": [ - "error", - "always" - ], - "operator-linebreak": [ - "error", - "after" - ], - "prefer-object-has-own": "off", - "prefer-template": "error", - "semi": [ - "error", - "never" - ], - "strict": "error", - "unicorn/explicit-length-check": "off", - "unicorn/filename-case": "off", - "unicorn/no-anonymous-default-export": "off", - "unicorn/no-array-callback-reference": "off", - "unicorn/no-array-method-this-argument": "off", - "unicorn/no-null": "off", - "unicorn/no-typeof-undefined": "off", - "unicorn/no-unused-properties": "error", - "unicorn/numeric-separators-style": "off", - "unicorn/prefer-array-flat": "off", - "unicorn/prefer-at": "off", - "unicorn/prefer-dom-node-dataset": "off", - "unicorn/prefer-global-this": "off", - "unicorn/prefer-module": "off", - "unicorn/prefer-query-selector": "off", - "unicorn/prefer-spread": "off", - "unicorn/prefer-string-raw": "off", - "unicorn/prefer-string-replace-all": "off", - "unicorn/prefer-structured-clone": "off", - "unicorn/prevent-abbreviations": "off" - }, - "overrides": [ - { - "files": [ - "build/**" - ], - "env": { - "browser": false, - "node": true - }, - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "no-console": "off", - "unicorn/prefer-top-level-await": "off" - } - }, - { - "files": [ - "js/**" - ], - "parserOptions": { - "sourceType": "module" - } - }, - { - "files": [ - "js/tests/*.js", - "js/tests/integration/rollup*.js" - ], - "env": { - "node": true - }, - "parserOptions": { - "sourceType": "script" - } - }, - { - "files": [ - "js/tests/unit/**" - ], - "env": { - "jasmine": true - }, - "rules": { - "no-console": "off", - "unicorn/consistent-function-scoping": "off", - "unicorn/no-useless-undefined": "off", - "unicorn/prefer-add-event-listener": "off" - } - }, - { - "files": [ - "js/tests/visual/**" - ], - "plugins": [ - "html" - ], - "settings": { - "html/html-extensions": [ - ".html" - ] - }, - "rules": { - "no-console": "off", - "no-new": "off", - "unicorn/no-array-for-each": "off" - } - }, - { - "files": [ - "scss/tests/**" - ], - "env": { - "node": true - }, - "parserOptions": { - "sourceType": "script" - } - }, - { - "files": [ - "site/**" - ], - "env": { - "browser": true, - "node": false - }, - "parserOptions": { - "sourceType": "script", - "ecmaVersion": 2019 - }, - "rules": { - "no-new": "off", - "unicorn/no-array-for-each": "off" - } - }, - { - "files": [ - "site/src/assets/application.js", - "site/src/assets/partials/*.js", - "site/src/assets/search.js", - "site/src/assets/snippets.js", - "site/src/assets/stackblitz.js", - "site/src/plugins/*.js" - ], - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2020 - } - }, - { - "files": [ - "site/src/assets/examples/cheatsheet/cheatsheet.js", - "site/src/assets/examples/sidebars/sidebars.js" - ], - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2020 - }, - "rules": { - "import/no-unresolved": "off" - } - }, - { - "files": [ - "**/*.md" - ], - "plugins": [ - "markdown" - ], - "processor": "markdown/markdown" - }, - { - "files": [ - "**/*.md/*.js", - "**/*.md/*.mjs" - ], - "extends": "plugin:markdown/recommended-legacy", - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "unicorn/prefer-node-protocol": "off" - } - } - ] -} diff --git a/eslint.config.js b/eslint.config.js index 02cbed068584..d807078449a1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -95,12 +95,12 @@ const localRules = { 'unicorn/no-unused-properties': 'error', 'unicorn/numeric-separators-style': 'off', 'unicorn/prefer-array-flat': 'off', - 'unicorn/prefer-at': 'off', + 'unicorn/prefer-at': 'error', 'unicorn/prefer-dom-node-dataset': 'off', 'unicorn/prefer-global-this': 'off', 'unicorn/prefer-module': 'off', 'unicorn/prefer-query-selector': 'off', - 'unicorn/prefer-spread': 'off', + 'unicorn/prefer-spread': 'error', 'unicorn/prefer-string-raw': 'off', 'unicorn/prefer-string-replace-all': 'off', 'unicorn/prefer-structured-clone': 'off', @@ -266,17 +266,14 @@ const eslintConfig = [ } }, - // site/** — browser, script mode, older ecmaVersion + // site/** — browser, script mode { files: ['site/**'], languageOptions: { globals: { ...globals.browser }, - sourceType: 'script', - parserOptions: { - ecmaVersion: 2019 - } + sourceType: 'script' }, rules: { 'no-new': 'off', @@ -296,10 +293,7 @@ const eslintConfig = [ 'site/src/plugins/*.js' ], languageOptions: { - sourceType: 'module', - parserOptions: { - ecmaVersion: 2020 - } + sourceType: 'module' }, // These files may have eslint-disable directives for the old import plugin linterOptions: { @@ -322,10 +316,7 @@ const eslintConfig = [ 'site/src/assets/examples/sidebars/sidebars.js' ], languageOptions: { - sourceType: 'module', - parserOptions: { - ecmaVersion: 2020 - } + sourceType: 'module' }, rules: { 'import/no-unresolved': 'off' diff --git a/js/src/combobox.js b/js/src/combobox.js index fad08043b7ed..9076e13a2418 100644 --- a/js/src/combobox.js +++ b/js/src/combobox.js @@ -359,7 +359,7 @@ class Combobox extends BaseComponent { const items = this._getVisibleItems() if (items.length > 0) { - const target = key === ARROW_DOWN_KEY ? items[0] : items[items.length - 1] + const target = key === ARROW_DOWN_KEY ? items[0] : items.at(-1) target.focus() } @@ -404,7 +404,7 @@ class Combobox extends BaseComponent { event.preventDefault() const items = this._getVisibleItems() if (items.length > 0) { - const targetItem = key === HOME_KEY ? items[0] : items[items.length - 1] + const targetItem = key === HOME_KEY ? items[0] : items.at(-1) targetItem.focus() } diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 10eee5e22469..6ab30c9bbe2d 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -23,7 +23,7 @@ export default { // can be removed later when multiple key/instances are fine to be used if (!instanceMap.has(key) && instanceMap.size !== 0) { // eslint-disable-next-line no-console - console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`) + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${[...instanceMap.keys()][0]}.`) return } diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index d4cee4d811a1..9e83d2064ab2 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -34,7 +34,7 @@ const getSelector = element => { const SelectorEngine = { find(selector, element = document.documentElement) { - return [].concat(...Element.prototype.querySelectorAll.call(element, selector)) + return [...Element.prototype.querySelectorAll.call(element, selector)] }, findOne(selector, element = document.documentElement) { @@ -42,7 +42,7 @@ const SelectorEngine = { }, children(element, selector) { - return [].concat(...element.children).filter(child => child.matches(selector)) + return [...element.children].filter(child => child.matches(selector)) }, parents(element, selector) { diff --git a/js/src/menu.js b/js/src/menu.js index 3689b3fdddf3..f70a13457606 100644 --- a/js/src/menu.js +++ b/js/src/menu.js @@ -186,7 +186,7 @@ class Menu extends BaseComponent { this._createFloating() if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { - for (const element of [].concat(...document.body.children)) { + for (const element of document.body.children) { EventHandler.on(element, 'mouseover', noop) } } @@ -249,7 +249,7 @@ class Menu extends BaseComponent { this._closeAllSubmenus() if ('ontouchstart' in document.documentElement) { - for (const element of [].concat(...document.body.children)) { + for (const element of document.body.children) { EventHandler.off(element, 'mouseover', noop) } } @@ -837,7 +837,7 @@ class Menu extends BaseComponent { .filter(element => isVisible(element)) if (items.length) { - const targetItem = key === HOME_KEY ? items[0] : items[items.length - 1] + const targetItem = key === HOME_KEY ? items[0] : items.at(-1) targetItem.focus() } diff --git a/js/src/otp-input.js b/js/src/otp-input.js index 6641eb39cd02..dffa1d97b31d 100644 --- a/js/src/otp-input.js +++ b/js/src/otp-input.js @@ -66,7 +66,7 @@ class OtpInput extends BaseComponent { } setValue(value) { - const chars = String(value).split('') + const chars = [...String(value)] for (const [index, input] of this._inputs.entries()) { input.value = chars[index] || '' } @@ -136,7 +136,7 @@ class OtpInput extends BaseComponent { // Handle multi-character input (some browsers/autofill) if (value.length > 1) { // Distribute characters across inputs - const chars = value.split('') + const chars = [...value] input.value = chars[0] || '' for (let i = 1; i < chars.length && index + i < this._inputs.length; i++) { diff --git a/js/src/tab.js b/js/src/tab.js index f02519eb97b8..70375ba25d11 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -162,7 +162,7 @@ class Tab extends BaseComponent { let nextActiveElement if ([HOME_KEY, END_KEY].includes(event.key)) { - nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1] + nextActiveElement = event.key === HOME_KEY ? children[0] : children.at(-1) } else { const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key) nextActiveElement = getNextActiveElement(children, event.target, isNext, true) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index bf5fadb97710..106085906dc7 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -242,7 +242,7 @@ class Tooltip extends BaseComponent { // only needed because of broken event delegation on iOS // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement) { - for (const element of [].concat(...document.body.children)) { + for (const element of document.body.children) { EventHandler.on(element, 'mouseover', noop) } } @@ -276,7 +276,7 @@ class Tooltip extends BaseComponent { // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { - for (const element of [].concat(...document.body.children)) { + for (const element of document.body.children) { EventHandler.off(element, 'mouseover', noop) } } diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index 158f3d1846d5..7900ab875a1d 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -97,7 +97,7 @@ class FocusTrap extends Config { if (elements.length === 0) { trapElement.focus() } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { - elements[elements.length - 1].focus() + elements.at(-1).focus() } else { elements[0].focus() } diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index bcd565a9cfef..cb33633ffd39 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -92,7 +92,7 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { const domParser = new window.DOMParser() const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html') - const elements = [].concat(...createdDocument.body.querySelectorAll('*')) + const elements = [...createdDocument.body.querySelectorAll('*')] for (const element of elements) { const elementName = element.nodeName.toLowerCase() @@ -102,8 +102,8 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { continue } - const attributeList = [].concat(...element.attributes) - const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []) + const attributeList = [...element.attributes] + const allowedAttributes = [...(allowList['*'] || []), ...(allowList[elementName] || [])] for (const attribute of attributeList) { if (!allowedAttribute(attribute, allowedAttributes)) { diff --git a/js/tests/integration/bundle-modularity.js b/js/tests/integration/bundle-modularity.js index d8f6fc0df844..574f54d219bd 100644 --- a/js/tests/integration/bundle-modularity.js +++ b/js/tests/integration/bundle-modularity.js @@ -2,6 +2,6 @@ import Tooltip from '../../dist/tooltip.js' import '../../dist/carousel.js' // eslint-disable-line import/no-unassigned-import window.addEventListener('load', () => { - [].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]')) + [...document.querySelectorAll('[data-bs-toggle="tooltip"]')] .map(tooltipNode => new Tooltip(tooltipNode)) }) diff --git a/js/tests/integration/bundle.js b/js/tests/integration/bundle.js index 0603bfdfb2dc..9f7404142838 100644 --- a/js/tests/integration/bundle.js +++ b/js/tests/integration/bundle.js @@ -1,6 +1,6 @@ import { Tooltip } from '../../../dist/js/bootstrap.js' window.addEventListener('load', () => { - [].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]')) + [...document.querySelectorAll('[data-bs-toggle="tooltip"]')] .map(tooltipNode => new Tooltip(tooltipNode)) }) diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js index f25d9fc3966b..fddea9c01fb1 100644 --- a/js/tests/unit/collapse.spec.js +++ b/js/tests/unit/collapse.spec.js @@ -130,7 +130,7 @@ describe('Collapse', () => { const collapseEl1 = fixtureEl.querySelector('#collapse1') const collapseEl2 = fixtureEl.querySelector('#collapse2') - const collapseList = [].concat(...fixtureEl.querySelectorAll('.collapse')) + const collapseList = [...fixtureEl.querySelectorAll('.collapse')] .map(el => new Collapse(el, { parent, toggle: false diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js index 95d9bf8ec9d8..f0ec5faf9a0b 100644 --- a/js/tests/unit/dom/selector-engine.spec.js +++ b/js/tests/unit/dom/selector-engine.spec.js @@ -68,7 +68,7 @@ describe('SelectorEngine', () => { ].join('') const list = fixtureEl.querySelector('ul') - const liList = [].concat(...fixtureEl.querySelectorAll('li')) + const liList = [...fixtureEl.querySelectorAll('li')] const result = SelectorEngine.children(list, 'li') expect(result).toEqual(liList) @@ -356,7 +356,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')]) }) it('should get elements if several ids are given', () => { @@ -368,7 +368,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')]) }) it('should get elements if several ids with special chars are given', () => { @@ -380,7 +380,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')]) }) it('should get elements in array, from href if no data-bs-target set', () => { @@ -392,7 +392,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')]) }) it('should return empty array if elements not found', () => { diff --git a/site/src/assets/examples/sidebars/sidebars.js b/site/src/assets/examples/sidebars/sidebars.js index 072c38e85b74..6b3177aa433e 100644 --- a/site/src/assets/examples/sidebars/sidebars.js +++ b/site/src/assets/examples/sidebars/sidebars.js @@ -1,6 +1,6 @@ import { Tooltip } from '../../dist/js/bootstrap.bundle.js' -const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')) +const tooltipTriggerList = [...document.querySelectorAll('[data-bs-toggle="tooltip"]')] tooltipTriggerList.forEach(tooltipTriggerEl => { new Tooltip(tooltipTriggerEl) }) diff --git a/site/src/assets/stackblitz.js b/site/src/assets/stackblitz.js index 1ca38d1949ef..a944ef8a0053 100644 --- a/site/src/assets/stackblitz.js +++ b/site/src/assets/stackblitz.js @@ -31,7 +31,7 @@ export default () => { const htmlSnippet = exampleEl.innerHTML const jsSnippet = codeSnippet.querySelector('.btn-edit').getAttribute('data-sb-js-snippet') // Get extra classes for this example - const classes = Array.from(exampleEl.classList).join(' ') + const classes = [...exampleEl.classList].join(' ') openBootstrapSnippet(htmlSnippet, jsSnippet, classes) })