diff --git a/.eslintrc b/.eslintrc index 17eef35ba7..5cd72ea708 100644 --- a/.eslintrc +++ b/.eslintrc @@ -65,22 +65,21 @@ "AssignmentExpression": { "array": false, "object": false } }], // identical to airbnb rule, except for allowing for..of, because we want to use it - // "no-restricted-syntax": [ - // "error", - // { - // "selector": "ForInStatement", - // "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." - // }, - // { - // "selector": "LabeledStatement", - // "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." - // }, - // { - // "selector": "WithStatement", - // "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." - // } - // ], - "no-restricted-syntax": "off", + "no-restricted-syntax": [ + "error", + { + "selector": "ForInStatement", + "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." + }, + { + "selector": "LabeledStatement", + "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." + }, + { + "selector": "WithStatement", + "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." + } + ], // allow lines of up to 120 characters // "max-len": ["error", { "code": 120, "tabWidth": 2, "ignoreUrls": true, "ignoreStrings": true, "ignoreTemplateLiterals": true, "ignoreRegExpLiterals": true }], "max-len": "off", // do this LAST diff --git a/install/web.js b/install/web.js index baf4a16a65..5c5d3d2d8c 100644 --- a/install/web.js +++ b/install/web.js @@ -151,9 +151,9 @@ function install(req, res) { req.setTimeout(0); installing = true; const setupEnvVars = nconf.get(); - for (const i in req.body) { - if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) { - setupEnvVars[i.replace(':', '__')] = req.body[i]; + for (const [key, value] of Object.entries(req.body)) { + if (!process.env.hasOwnProperty(key)) { + setupEnvVars[key.replace(':', '__')] = value; } } @@ -161,12 +161,12 @@ function install(req, res) { const pushToRoot = function (parentKey, key) { setupEnvVars[`${parentKey}__${key}`] = setupEnvVars[parentKey][key]; }; - for (const j in setupEnvVars) { - if (setupEnvVars.hasOwnProperty(j) && typeof setupEnvVars[j] === 'object' && setupEnvVars[j] !== null && !Array.isArray(setupEnvVars[j])) { - Object.keys(setupEnvVars[j]).forEach(pushToRoot.bind(null, j)); - delete setupEnvVars[j]; - } else if (Array.isArray(setupEnvVars[j])) { - setupEnvVars[j] = JSON.stringify(setupEnvVars[j]); + for (const [parentKey, value] of Object.entries(setupEnvVars)) { + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + Object.keys(value).forEach(key => pushToRoot(parentKey, key)); + delete setupEnvVars[parentKey]; + } else if (Array.isArray(value)) { + setupEnvVars[parentKey] = JSON.stringify(value); } } diff --git a/src/analytics.js b/src/analytics.js index 3b3a995b34..dbc601b689 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -128,13 +128,9 @@ Analytics.writeData = async function () { uniqueIPCount = 0; } - if (Object.keys(counters).length > 0) { - for (const key in counters) { - if (counters.hasOwnProperty(key)) { - dbQueue.push(db.sortedSetIncrBy(`analytics:${key}`, counters[key], today.getTime())); - delete counters[key]; - } - } + for (const [key, value] of Object.entries(counters)) { + dbQueue.push(db.sortedSetIncrBy(`analytics:${key}`, value, today.getTime())); + delete counters[key]; } try { await Promise.all(dbQueue); diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 60f18381a5..4259771b2e 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -28,9 +28,9 @@ helpers.fieldToString = function (field) { helpers.serializeData = function (data) { const serialized = {}; - for (const field in data) { - if (data.hasOwnProperty(field) && field !== '') { - serialized[helpers.fieldToString(field)] = data[field]; + for (const [field, value] of Object.entries(data)) { + if (field !== '') { + serialized[helpers.fieldToString(field)] = value; } } return serialized; @@ -38,10 +38,8 @@ helpers.serializeData = function (data) { helpers.deserializeData = function (data) { const deserialized = {}; - for (const field in data) { - if (data.hasOwnProperty(field)) { - deserialized[field.replace(/\uff0E/g, '.')] = data[field]; - } + for (const [field, value] of Object.entries(data)) { + deserialized[field.replace(/\uff0E/g, '.')] = value; } return deserialized; }; diff --git a/src/flags.js b/src/flags.js index 913e21543d..3fd707058d 100644 --- a/src/flags.js +++ b/src/flags.js @@ -135,13 +135,11 @@ Flags.getFlagIdsWithFilters = async function ({ filters, uid }) { filters.page = filters.hasOwnProperty('page') ? Math.abs(parseInt(filters.page, 10) || 1) : 1; filters.perPage = filters.hasOwnProperty('perPage') ? Math.abs(parseInt(filters.perPage, 10) || 20) : 20; - for (const type in filters) { - if (filters.hasOwnProperty(type)) { - if (Flags._filters.hasOwnProperty(type)) { - Flags._filters[type](sets, orSets, filters[type], uid); - } else { - winston.warn(`[flags/list] No flag filter type found: ${type}`); - } + for (const type of Object.keys(filters)) { + if (Flags._filters.hasOwnProperty(type)) { + Flags._filters[type](sets, orSets, filters[type], uid); + } else { + winston.warn(`[flags/list] No flag filter type found: ${type}`); } } sets = (sets.length || orSets.length) ? sets : ['flags:datetime']; // No filter default @@ -586,29 +584,27 @@ Flags.update = async function (flagId, uid, changeset) { // Retrieve existing flag data to compare for history-saving/reference purposes const tasks = []; - for (const prop in changeset) { - if (changeset.hasOwnProperty(prop)) { - if (current[prop] === changeset[prop]) { + for (const prop of Object.keys(changeset)) { + if (current[prop] === changeset[prop]) { + delete changeset[prop]; + } else if (prop === 'state') { + if (!Flags._constants.states.includes(changeset[prop])) { delete changeset[prop]; - } else if (prop === 'state') { - if (!Flags._constants.states.includes(changeset[prop])) { - delete changeset[prop]; - } else { - tasks.push(db.sortedSetAdd(`flags:byState:${changeset[prop]}`, now, flagId)); - tasks.push(db.sortedSetRemove(`flags:byState:${current[prop]}`, flagId)); - if (changeset[prop] === 'resolved' || changeset[prop] === 'rejected') { - tasks.push(notifications.rescind(`flag:${current.type}:${current.targetId}`)); - } - } - } else if (prop === 'assignee') { - /* eslint-disable-next-line */ - if (!await isAssignable(parseInt(changeset[prop], 10))) { - delete changeset[prop]; - } else { - tasks.push(db.sortedSetAdd(`flags:byAssignee:${changeset[prop]}`, now, flagId)); - tasks.push(notifyAssignee(changeset[prop])); + } else { + tasks.push(db.sortedSetAdd(`flags:byState:${changeset[prop]}`, now, flagId)); + tasks.push(db.sortedSetRemove(`flags:byState:${current[prop]}`, flagId)); + if (changeset[prop] === 'resolved' || changeset[prop] === 'rejected') { + tasks.push(notifications.rescind(`flag:${current.type}:${current.targetId}`)); } } + } else if (prop === 'assignee') { + /* eslint-disable-next-line */ + if (!await isAssignable(parseInt(changeset[prop], 10))) { + delete changeset[prop]; + } else { + tasks.push(db.sortedSetAdd(`flags:byAssignee:${changeset[prop]}`, now, flagId)); + tasks.push(notifyAssignee(changeset[prop])); + } } } diff --git a/src/install.js b/src/install.js index ee12efc3d8..41afe5e27d 100644 --- a/src/install.js +++ b/src/install.js @@ -150,12 +150,7 @@ async function setupConfig() { async function completeConfigSetup(config) { // Add CI object if (install.ciVals) { - config.test_database = {}; - for (const prop in install.ciVals) { - if (install.ciVals.hasOwnProperty(prop)) { - config.test_database[prop] = install.ciVals[prop]; - } - } + config.test_database = { ...install.ciVals }; } // Add package_manager object if set diff --git a/src/meta/settings.js b/src/meta/settings.js index 4936f9348c..63e7fe47f2 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -47,12 +47,10 @@ Settings.set = async function (hash, values, quiet) { ({ plugin: hash, settings: values, quiet } = await plugins.hooks.fire('filter:settings.set', { plugin: hash, settings: values, quiet })); const sortedListData = {}; - for (const key in values) { - if (values.hasOwnProperty(key)) { - if (Array.isArray(values[key]) && typeof values[key][0] !== 'string') { - sortedListData[key] = values[key]; - delete values[key]; - } + for (const [key, value] of Object.entries(values)) { + if (Array.isArray(value) && typeof value[0] !== 'string') { + sortedListData[key] = value; + delete values[key]; } } const sortedLists = Object.keys(sortedListData); diff --git a/src/middleware/headers.js b/src/middleware/headers.js index 6d46e8f21a..e06082851c 100644 --- a/src/middleware/headers.js +++ b/src/middleware/headers.js @@ -64,9 +64,9 @@ module.exports = function (middleware) { headers['X-Upstream-Hostname'] = os.hostname(); } - for (const key in headers) { - if (headers.hasOwnProperty(key) && headers[key]) { - res.setHeader(key, headers[key]); + for (const [key, value] of Object.entries(headers)) { + if (value) { + res.setHeader(key, value); } } diff --git a/src/plugins/index.js b/src/plugins/index.js index e0fa4f6d45..c03870fa37 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -241,13 +241,7 @@ Plugins.normalise = async function (apiReturn) { pluginMap[plugin.id].outdated = semver.gt(pluginMap[plugin.id].latest, pluginMap[plugin.id].version); }); - const pluginArray = []; - - for (const key in pluginMap) { - if (pluginMap.hasOwnProperty(key)) { - pluginArray.push(pluginMap[key]); - } - } + const pluginArray = Object.values(pluginMap); pluginArray.sort((a, b) => { if (a.name > b.name) { diff --git a/src/settings.js b/src/settings.js index 937aacf307..a8346fcb87 100644 --- a/src/settings.js +++ b/src/settings.js @@ -4,23 +4,16 @@ const meta = require('./meta'); const pubsub = require('./pubsub'); function expandObjBy(obj1, obj2) { - let key; - let val1; - let val2; - let xorValIsArray; let changed = false; - for (key in obj2) { - if (obj2.hasOwnProperty(key)) { - val2 = obj2[key]; - val1 = obj1[key]; - xorValIsArray = Array.isArray(val1) === Array.isArray(val2); - if (xorValIsArray || !obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) { - obj1[key] = val2; + for (const [key, val2] of Object.entries(obj2)) { + const val1 = obj1[key]; + const xorIsArray = Array.isArray(val1) === Array.isArray(val2); + if (xorIsArray || !obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) { + obj1[key] = val2; + changed = true; + } else if (typeof val2 === 'object' && !Array.isArray(val2)) { + if (expandObjBy(val1, val2)) { changed = true; - } else if (typeof val2 === 'object' && !Array.isArray(val2)) { - if (expandObjBy(val1, val2)) { - changed = true; - } } } } @@ -28,16 +21,11 @@ function expandObjBy(obj1, obj2) { } function trim(obj1, obj2) { - let key; - let val1; - for (key in obj1) { - if (obj1.hasOwnProperty(key)) { - val1 = obj1[key]; - if (!obj2.hasOwnProperty(key)) { - delete obj1[key]; - } else if (typeof val1 === 'object' && !Array.isArray(val1)) { - trim(val1, obj2[key]); - } + for (const [key, val1] of Object.entries(obj1)) { + if (!obj2.hasOwnProperty(key)) { + delete obj1[key]; + } else if (typeof val1 === 'object' && !Array.isArray(val1)) { + trim(val1, obj2[key]); } } } diff --git a/src/socket.io/admin/config.js b/src/socket.io/admin/config.js index f22bcd0609..f08aa1b186 100644 --- a/src/socket.io/admin/config.js +++ b/src/socket.io/admin/config.js @@ -32,15 +32,10 @@ Config.setMultiple = async function (socket, data) { } }); await meta.configs.setMultiple(data); - for (const field in data) { - if (data.hasOwnProperty(field)) { - const setting = { - key: field, - value: data[field], - }; - plugins.hooks.fire('action:config.set', setting); - logger.monitorConfig({ io: index.server }, setting); - } + for (const [key, value] of Object.entries(data)) { + const setting = { key, value }; + plugins.hooks.fire('action:config.set', setting); + logger.monitorConfig({ io: index.server }, setting); } if (Object.keys(changes).length) { changes.type = 'config-change'; diff --git a/src/socket.io/admin/rooms.js b/src/socket.io/admin/rooms.js index 7b688a35b9..4aafe9fbf6 100644 --- a/src/socket.io/admin/rooms.js +++ b/src/socket.io/admin/rooms.js @@ -64,22 +64,20 @@ SocketRooms.getAll = async function () { category: 0, }; - for (const instance in stats) { - if (stats.hasOwnProperty(instance)) { - totals.onlineGuestCount += stats[instance].onlineGuestCount; - totals.onlineRegisteredCount += stats[instance].onlineRegisteredCount; - totals.socketCount += stats[instance].socketCount; - totals.users.categories += stats[instance].users.categories; - totals.users.recent += stats[instance].users.recent; - totals.users.unread += stats[instance].users.unread; - totals.users.topics += stats[instance].users.topics; - totals.users.category += stats[instance].users.category; - - stats[instance].topics.forEach((topic) => { - totals.topics[topic.tid] = totals.topics[topic.tid] || { count: 0, tid: topic.tid }; - totals.topics[topic.tid].count += topic.count; - }); - } + for (const instance of Object.values(stats)) { + totals.onlineGuestCount += instance.onlineGuestCount; + totals.onlineRegisteredCount += instance.onlineRegisteredCount; + totals.socketCount += instance.socketCount; + totals.users.categories += instance.users.categories; + totals.users.recent += instance.users.recent; + totals.users.unread += instance.users.unread; + totals.users.topics += instance.users.topics; + totals.users.category += instance.users.category; + + instance.topics.forEach((topic) => { + totals.topics[topic.tid] = totals.topics[topic.tid] || { count: 0, tid: topic.tid }; + totals.topics[topic.tid].count += topic.count; + }); } let topTenTopics = []; diff --git a/src/user/data.js b/src/user/data.js index 2f94f24c2a..bccea4c373 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -285,10 +285,8 @@ module.exports = function (User) { User.setUserFields = async function (uid, data) { await db.setObject(`user:${uid}`, data); - for (const field in data) { - if (data.hasOwnProperty(field)) { - plugins.hooks.fire('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' }); - } + for (const [field, value] of Object.entries(data)) { + plugins.hooks.fire('action:user.set', { uid, field, value, type: 'set' }); } }; diff --git a/src/user/jobs.js b/src/user/jobs.js index ce5808ac26..8e069f0435 100644 --- a/src/user/jobs.js +++ b/src/user/jobs.js @@ -46,13 +46,11 @@ module.exports = function (User) { User.stopJobs = function () { let terminated = 0; // Terminate any active cron jobs - for (const jobId in jobs) { - if (jobs.hasOwnProperty(jobId)) { - winston.verbose(`[user/jobs] Terminating job (${jobId})`); - jobs[jobId].stop(); - delete jobs[jobId]; - terminated += 1; - } + for (const jobId of Object.keys(jobs)) { + winston.verbose(`[user/jobs] Terminating job (${jobId})`); + jobs[jobId].stop(); + delete jobs[jobId]; + terminated += 1; } if (terminated > 0) { winston.verbose(`[user/jobs] ${terminated} jobs terminated`); diff --git a/src/user/reset.js b/src/user/reset.js index 1fa3ff9082..6942dcb02b 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -131,8 +131,8 @@ UserReset.cleanByUid = async function (uid) { await batch.processSortedSet('reset:issueDate', async (tokens) => { const results = await db.getObjectFields('reset:uid', tokens); - for (const code in results) { - if (results.hasOwnProperty(code) && parseInt(results[code], 10) === uid) { + for (const [code, result] of Object.entries(results)) { + if (parseInt(result, 10) === uid) { tokensToClean.push(code); } } diff --git a/src/webserver.js b/src/webserver.js index 743fd6dda7..a75549420a 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -70,10 +70,8 @@ server.on('connection', (conn) => { exports.destroy = function (callback) { server.close(callback); - for (const key in connections) { - if (connections.hasOwnProperty(key)) { - connections[key].destroy(); - } + for (const connection of Object.values(connections)) { + connection.destroy(); } }; diff --git a/src/widgets/index.js b/src/widgets/index.js index d169876b85..eac16373fc 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -196,13 +196,8 @@ widgets.reset = async function () { }; widgets.resetTemplate = async function (template) { - let toBeDrafted = []; const area = await db.getObject(`widgets:${template}.tpl`); - for (const location in area) { - if (area.hasOwnProperty(location)) { - toBeDrafted = toBeDrafted.concat(JSON.parse(area[location])); - } - } + const toBeDrafted = _.flatMap(Object.values(area), value => JSON.parse(value)); await db.delete(`widgets:${template}.tpl`); let draftWidgets = await db.getObjectField('widgets:global', 'drafts'); draftWidgets = JSON.parse(draftWidgets).concat(toBeDrafted); diff --git a/test/api.js b/test/api.js index fb32d5f998..e3623412b0 100644 --- a/test/api.js +++ b/test/api.js @@ -420,11 +420,9 @@ describe('API', async () => { return memo; }, {}); - for (const header in expectedHeaders) { - if (expectedHeaders.hasOwnProperty(header)) { - assert(response.headers[header.toLowerCase()]); - assert.strictEqual(response.headers[header.toLowerCase()], expectedHeaders[header]); - } + for (const header of Object.keys(expectedHeaders)) { + assert(response.headers[header.toLowerCase()]); + assert.strictEqual(response.headers[header.toLowerCase()], expectedHeaders[header]); } return; } diff --git a/test/flags.js b/test/flags.js index f2bf1e5cc0..0f9b48ac0c 100644 --- a/test/flags.js +++ b/test/flags.js @@ -55,11 +55,9 @@ describe('Flags', () => { target_readable: 'Post 1', }; assert(flagData); - for (const key in compare) { - if (compare.hasOwnProperty(key)) { - assert.ok(flagData[key], `undefined key ${key}`); - assert.equal(flagData[key], compare[key]); - } + for (const key of Object.keys(compare)) { + assert.ok(flagData[key], `undefined key ${key}`); + assert.equal(flagData[key], compare[key]); } done(); @@ -131,11 +129,9 @@ describe('Flags', () => { target_readable: 'Post 1', }; assert(flagData); - for (const key in compare) { - if (compare.hasOwnProperty(key)) { - assert.ok(flagData[key], `undefined key ${key}`); - assert.equal(flagData[key], compare[key]); - } + for (const key of Object.keys(compare)) { + assert.ok(flagData[key], `undefined key ${key}`); + assert.equal(flagData[key], compare[key]); } done(); @@ -483,11 +479,9 @@ describe('Flags', () => { content: 'This is flaggable content', }; - for (const key in compare) { - if (compare.hasOwnProperty(key)) { - assert.ok(data[key]); - assert.equal(data[key], compare[key]); - } + for (const key of Object.keys(compare)) { + assert.ok(data[key]); + assert.equal(data[key], compare[key]); } done(); @@ -503,11 +497,9 @@ describe('Flags', () => { email: 'b@c.com', }; - for (const key in compare) { - if (compare.hasOwnProperty(key)) { - assert.ok(data[key]); - assert.equal(data[key], compare[key]); - } + for (const key of Object.keys(compare)) { + assert.ok(data[key]); + assert.equal(data[key], compare[key]); } done(); @@ -644,11 +636,9 @@ describe('Flags', () => { }; const data = notes[1]; - for (const key in compare) { - if (compare.hasOwnProperty(key)) { - assert.ok(data[key]); - assert.strictEqual(data[key], compare[key]); - } + for (const key of Object.keys(compare)) { + assert.ok(data[key]); + assert.strictEqual(data[key], compare[key]); } done();