diff --git a/eslint.config.cjs b/eslint.config.mjs similarity index 77% rename from eslint.config.cjs rename to eslint.config.mjs index fb29b4001f..6a5ed91eb0 100644 --- a/eslint.config.cjs +++ b/eslint.config.mjs @@ -1,11 +1,14 @@ 'use strict'; -const serverConfig = require('eslint-config-nodebb'); -const publicConfig = require('eslint-config-nodebb/public'); -const { configs } = require('@eslint/js'); -const globals = require('globals'); +import serverConfig from 'eslint-config-nodebb'; +import publicConfig from 'eslint-config-nodebb/public'; -module.exports = [ +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; + +import js from '@eslint/js'; + +export default defineConfig([ { ignores: [ 'node_modules/', @@ -27,7 +30,6 @@ module.exports = [ 'install/docker/', ], }, - configs.recommended, { rules: { 'no-bitwise': 'warn', @@ -58,5 +60,5 @@ module.exports = [ }, ...publicConfig, ...serverConfig -]; +]); diff --git a/install/package.json b/install/package.json index 38e73c580c..63c1c4bb1a 100644 --- a/install/package.json +++ b/install/package.json @@ -162,7 +162,8 @@ "@commitlint/config-angular": "19.8.0", "coveralls": "3.1.1", "@eslint/js": "9.24.0", - "eslint-config-nodebb": "1.0.7", + "@stylistic/eslint-plugin-js": "4.2.0", + "eslint-config-nodebb": "1.1.0", "eslint-plugin-import": "2.31.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", diff --git a/public/src/admin/extend/rewards.js b/public/src/admin/extend/rewards.js index 805b217193..25119c3a27 100644 --- a/public/src/admin/extend/rewards.js +++ b/public/src/admin/extend/rewards.js @@ -84,14 +84,10 @@ define('admin/extend/rewards', [ let inputs; let html = ''; - for (const reward in available) { - if (available.hasOwnProperty(reward)) { - if (available[reward].rid === el.attr('data-selected')) { - inputs = available[reward].inputs; - parent.attr('data-rid', available[reward].rid); - break; - } - } + const selectedReward = available.find(reward => reward.rid === el.attr('data-selected')); + if (selectedReward) { + inputs = selectedReward.inputs; + parent.attr('data-rid', selectedReward.rid); } if (!inputs) { @@ -122,10 +118,8 @@ define('admin/extend/rewards', [ const div = $(this).find('.inputs'); const rewards = active[i].rewards; - for (const reward in rewards) { - if (rewards.hasOwnProperty(reward)) { - div.find('[name="' + reward + '"]').val(rewards[reward]); - } + for (const [reward, value] of Object.entries(rewards)) { + div.find('[name="' + reward + '"]').val(value); } }); } diff --git a/public/src/admin/extend/widgets.js b/public/src/admin/extend/widgets.js index 1238ee772b..3171837988 100644 --- a/public/src/admin/extend/widgets.js +++ b/public/src/admin/extend/widgets.js @@ -117,23 +117,21 @@ define('admin/extend/widgets', [ area.find('.widget-panel[data-widget]').each(function () { const widgetData = {}; const data = $(this).find('form').serializeArray(); - - for (const d in data) { - if (data.hasOwnProperty(d)) { - if (data[d].name) { - if (widgetData[data[d].name]) { - if (!Array.isArray(widgetData[data[d].name])) { - widgetData[data[d].name] = [ - widgetData[data[d].name], - ]; - } - widgetData[data[d].name].push(data[d].value); - } else { - widgetData[data[d].name] = data[d].value; + data.forEach((widgetField) => { + const { name, value } = widgetField; + if (name) { + if (widgetData[name]) { + if (!Array.isArray(widgetData[name])) { + widgetData[name] = [ + widgetData[name], + ]; } + widgetData[name].push(value); + } else { + widgetData[name] = value; } } - } + }); widgets.push({ widget: $(this).attr('data-widget'), diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index 0721d8c209..247f9646b2 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -215,10 +215,8 @@ define('admin/settings', [ return callback(err); } - for (const field in data) { - if (data.hasOwnProperty(field)) { - app.config[field] = data[field]; - } + for (const [field, value] of Object.entries(data)) { + app.config[field] = value; } callback(); diff --git a/public/src/app.js b/public/src/app.js index 1ba3a4093c..cd521b26b6 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -192,7 +192,6 @@ if (document.readyState === 'loading') { const pageParams = utils.params(); function queryMatch(search) { const mySearchParams = new URLSearchParams(search); - // eslint-disable-next-line no-restricted-syntax for (const [key, value] of mySearchParams) { if (pageParams[key] === value) { return true; diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 1d6f1c1652..0c079d5030 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -75,17 +75,15 @@ define('forum/account/settings', [ api.put(`/users/${ajaxify.data.uid}/settings`, { settings }).then((newSettings) => { alerts.success('[[success:settings-saved]]'); let languageChanged = false; - for (const key in newSettings) { - if (newSettings.hasOwnProperty(key)) { - if (key === 'userLang' && config.userLang !== newSettings.userLang) { - languageChanged = true; - } - if (key === 'bootswatchSkin') { - savedSkin = newSettings.bootswatchSkin; - config.bootswatchSkin = savedSkin === 'noskin' ? '' : savedSkin; - } else if (config.hasOwnProperty(key)) { - config[key] = newSettings[key]; - } + for (const [key, value] of Object.entries(newSettings)) { + if (key === 'userLang' && config.userLang !== newSettings.userLang) { + languageChanged = true; + } + if (key === 'bootswatchSkin') { + savedSkin = newSettings.bootswatchSkin; + config.bootswatchSkin = savedSkin === 'noskin' ? '' : savedSkin; + } else if (config.hasOwnProperty(key)) { + config[key] = value; } } diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js index 110ef1f334..8d13124280 100644 --- a/public/src/client/flags/list.js +++ b/public/src/client/flags/list.js @@ -103,10 +103,8 @@ export function enableFilterForm() { }); } else { // Persona; parse ajaxify data to set form values to reflect current filters - for (const filter in ajaxify.data.filters) { - if (ajaxify.data.filters.hasOwnProperty(filter)) { - $filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]); - } + for (const [filter, value] of Object.entries(ajaxify.data.filters)) { + $filtersEl.find('[name="' + filter + '"]').val(value); } $filtersEl.find('[name="sort"]').val(ajaxify.data.sort); diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index 5b45deeb54..4a8a36a768 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -50,18 +50,14 @@ define('forum/topic/events', [ Events.init = function () { Events.removeListeners(); - for (const eventName in events) { - if (events.hasOwnProperty(eventName)) { - socket.on(eventName, events[eventName]); - } + for (const [eventName, handler] of Object.entries(events)) { + socket.on(eventName, handler); } }; Events.removeListeners = function () { - for (const eventName in events) { - if (events.hasOwnProperty(eventName)) { - socket.removeListener(eventName, events[eventName]); - } + for (const [eventName, handler] of Object.entries(events)) { + socket.removeListener(eventName, handler); } }; diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index 454fb441c2..183c1bbb70 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -180,14 +180,12 @@ module.exports = function (utils, Benchpress, relative_path) { function spawnPrivilegeStates(cid, member, privileges, types) { const states = []; - for (const priv in privileges) { - if (privileges.hasOwnProperty(priv)) { - states.push({ - name: priv, - state: privileges[priv], - type: types[priv], - }); - } + for (const [priv, state] of Object.entries(privileges)) { + states.push({ + name: priv, + state: state, + type: types[priv], + }); } return states.map(function (priv) { const guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login', 'groups:group:create']; diff --git a/public/src/modules/iconSelect.js b/public/src/modules/iconSelect.js index 674e8caf41..409ddb059b 100644 --- a/public/src/modules/iconSelect.js +++ b/public/src/modules/iconSelect.js @@ -255,7 +255,6 @@ define('iconSelect', ['benchpress', 'bootbox'], function (Benchpress, bootbox) { className: 'btn-default', callback: function () { el.removeClass(selected.icon); - // eslint-disable-next-line no-restricted-syntax for (const style of selected.styles) { el.removeClass(style); } @@ -272,11 +271,9 @@ define('iconSelect', ['benchpress', 'bootbox'], function (Benchpress, bootbox) { const newIcon = cleanFAClass(iconClass); if (newIcon.icon) { el.removeClass(selected.icon).addClass(newIcon.icon); - // eslint-disable-next-line no-restricted-syntax for (const style of selected.styles || []) { el.removeClass(style); } - // eslint-disable-next-line no-restricted-syntax for (const style of newIcon.styles || []) { el.addClass(style); } @@ -391,7 +388,6 @@ define('iconSelect', ['benchpress', 'bootbox'], function (Benchpress, bootbox) { function cleanFAClass(classList) { const styles = []; let icon; - // eslint-disable-next-line no-restricted-syntax for (const className of classList) { if (className.startsWith('fa-') && !excludedClassRegex.test(className)) { if (styleRegex.test(className)) { diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 9d97f1fc6a..ad823ae17e 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -267,7 +267,7 @@ define('search', [ function createQueryString(data) { const searchIn = data.in || 'titles'; - let term = data.term.replace(/^[ ?#]*/, ''); + const term = data.term.replace(/^[ ?#]*/, ''); const query = { ...data, diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 148f72d182..bb54ba7436 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -2,12 +2,9 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { - let Settings; let onReady = []; let waitingJobs = 0; - let helper; - /** Returns the hook of given name that matches the given type or element. @param type The type of the element to get the matching hook for, or the element itself. @@ -29,7 +26,7 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { return null; } - helper = { + const helper = { /** @returns Object A deep clone of the given object. */ @@ -48,10 +45,8 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { */ createElement: function (tagName, data, text) { const element = document.createElement(tagName); - for (const k in data) { - if (data.hasOwnProperty(k)) { - element.setAttribute(k, data[k]); - } + for (const [k, val] of Object.entries(data)) { + element.setAttribute(k, val); } if (text) { element.appendChild(document.createTextNode(text)); @@ -331,7 +326,7 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { }, }; - Settings = { + const Settings = { helper: helper, plugins: {}, cfg: {}, diff --git a/public/src/modules/settings/array.js b/public/src/modules/settings/array.js index 161d817b40..2867011115 100644 --- a/public/src/modules/settings/array.js +++ b/public/src/modules/settings/array.js @@ -45,16 +45,13 @@ define('settings/array', function () { element.attr('data-parent', '_' + key); delete attributes['data-type']; delete attributes.tagName; - for (const name in attributes) { - if (attributes.hasOwnProperty(name)) { - const val = attributes[name]; - if (name.search('data-') === 0) { - element.data(name.substring(5), val); - } else if (name.search('prop-') === 0) { - element.prop(name.substring(5), val); - } else { - element.attr(name, val); - } + for (const [name, val] of Object.entries(attributes)) { + if (name.search('data-') === 0) { + element.data(name.substring(5), val); + } else if (name.search('prop-') === 0) { + element.prop(name.substring(5), val); + } else { + element.attr(name, val); } } helper.fillField(element, value); diff --git a/public/src/modules/settings/object.js b/public/src/modules/settings/object.js index 4d0a57645b..9a94950055 100644 --- a/public/src/modules/settings/object.js +++ b/public/src/modules/settings/object.js @@ -25,16 +25,13 @@ define('settings/object', function () { element.attr('data-prop', prop); delete attributes['data-type']; delete attributes.tagName; - for (const name in attributes) { - if (attributes.hasOwnProperty(name)) { - const val = attributes[name]; - if (name.search('data-') === 0) { - element.data(name.substring(5), val); - } else if (name.search('prop-') === 0) { - element.prop(name.substring(5), val); - } else { - element.attr(name, val); - } + for (const [name, val] of Object.entries(attributes)) { + if (name.search('data-') === 0) { + element.data(name.substring(5), val); + } else if (name.search('prop-') === 0) { + element.prop(name.substring(5), val); + } else { + element.attr(name, val); } } helper.fillField(element, value); @@ -62,9 +59,7 @@ define('settings/object', function () { const properties = element.data('attributes') || element.data('properties'); const key = element.data('key') || element.data('parent'); let separator = element.data('split') || ', '; - let propertyIndex; - let propertyName; - let attributes; + separator = (function () { try { return $(separator); @@ -77,27 +72,26 @@ define('settings/object', function () { if (typeof value !== 'object') { value = {}; } + if (Array.isArray(properties)) { - for (propertyIndex in properties) { - if (properties.hasOwnProperty(propertyIndex)) { - attributes = properties[propertyIndex]; - if (typeof attributes !== 'object') { - attributes = {}; - } - propertyName = attributes['data-prop'] || attributes['data-property'] || propertyIndex; - if (value[propertyName] === undefined && attributes['data-new'] !== undefined) { - value[propertyName] = attributes['data-new']; - } - addObjectPropertyElement( - element, - key, - attributes, - propertyName, - value[propertyName], - separator.clone(), - function (el) { element.append(el); } - ); + for (const [propertyIndex, attr] of Object.entries(properties)) { + let attributes = attr; + if (typeof attr !== 'object') { + attributes = {}; } + const propertyName = attributes['data-prop'] || attributes['data-property'] || propertyIndex; + if (value[propertyName] === undefined && attributes['data-new'] !== undefined) { + value[propertyName] = attributes['data-new']; + } + addObjectPropertyElement( + element, + key, + attributes, + propertyName, + value[propertyName], + separator.clone(), + function (el) { element.append(el); } + ); } } }, diff --git a/public/src/modules/settings/sorted-list.js b/public/src/modules/settings/sorted-list.js index 2ff9bbc82f..707a679c02 100644 --- a/public/src/modules/settings/sorted-list.js +++ b/public/src/modules/settings/sorted-list.js @@ -64,7 +64,6 @@ define('settings/sorted-list', [ })); // todo: parse() needs to be refactored to return the html, so multiple calls can be parallelized - // eslint-disable-next-line no-restricted-syntax for (const { itemUUID, item } of items) { // eslint-disable-next-line no-await-in-loop const element = await parse($container, itemUUID, item); diff --git a/public/src/utils.common.js b/public/src/utils.common.js index 2e916e7768..66785a6467 100644 --- a/public/src/utils.common.js +++ b/public/src/utils.common.js @@ -631,7 +631,6 @@ const utils = { try { str = JSON.parse(str); - // eslint-disable-next-line no-unused-vars } catch (err) { /* empty */ } return str; diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 31e0403c4a..3e05aa413d 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -646,7 +646,7 @@ Mocks.notes.public = async (post) => { content: post.content, published, attachment: normalizeAttachment(noteAttachment), - } + }; } let context = await posts.getPostField(post.pid, 'context'); diff --git a/src/meta/errors.js b/src/meta/errors.js index 0b7740b4ae..d1e11d67d8 100644 --- a/src/meta/errors.js +++ b/src/meta/errors.js @@ -4,7 +4,7 @@ const nconf = require('nconf'); const winston = require('winston'); const validator = require('validator'); const cronJob = require('cron').CronJob; -const { setTimeout } = require('timers/promises') +const { setTimeout } = require('timers/promises'); const db = require('../database'); const analytics = require('../analytics'); diff --git a/src/utils.js b/src/utils.js index bcacaa565e..2b620b91e5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -46,7 +46,7 @@ utils.getSass = function () { const sass = require('sass-embedded'); return sass; } catch (err) { - console.error(err.message) + console.error(err.message); return require('sass'); } }; diff --git a/test/utils.js b/test/utils.js index 353ab3f83a..637ab4ab31 100644 --- a/test/utils.js +++ b/test/utils.js @@ -285,7 +285,6 @@ describe('Utility Methods', () => { }); it('should return passed in value if invalid', (done) => { - // eslint-disable-next-line no-loss-of-precision const bigInt = -111111111111111111; const result = utils.toISOString(bigInt); assert.equal(bigInt, result);