diff --git a/.eslintrc-preferred.yml b/.eslintrc-preferred.yml
index 23766a71a83633c958ccbd3d3160f49b116de4b1..f08d82881046fe5e37e86723f8ed0786d98bf153 100644
--- a/.eslintrc-preferred.yml
+++ b/.eslintrc-preferred.yml
@@ -20,7 +20,10 @@ rules:
   no-var: "error"
   prefer-const: "error"
   strict: "error"
+  arrow-body-style: ["error", "as-needed"]
 
   # Chai friendly.
   no-unused-expressions: "off"
   chai-friendly/no-unused-expressions: "error"
+
+  mocha/prefer-arrow-callback: "error"
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 1d9b2c6e5f6a71f3a89b7d940fe45e080719bc93..6a09b28ea259fdba4870d753ca538238c3745815 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -7,6 +7,9 @@ env:
   es6: true
   mocha: true
 
+plugins:
+  - mocha
+
 extends:
   # Enable a set of unopinionated, style-agnostic rules which cover many
   # likely errors.
@@ -28,3 +31,6 @@ rules:
   eol-last: "error"
   object-curly-spacing: ["error", "always"]
   comma-dangle: ["error", "always-multiline"]
+
+  mocha/no-exclusive-tests: "error"
+  mocha/no-mocha-arrows: "error"
diff --git a/frontend/components/search-results.js b/frontend/components/search-results.js
index 5343f39659c35c835908642f37b8ffdba19537a1..9436e619346171ab2ee8b0dea78e6313e845acb0 100644
--- a/frontend/components/search-results.js
+++ b/frontend/components/search-results.js
@@ -32,16 +32,11 @@ export default class SearchResults extends React.Component {
   }
 
   renderCategoryHeadings() {
-    return this.preparedExamples.map(function(category, i) {
-      return (
-        <Link
-          to={'/examples/' + category.category.id}
-          key={category.category.id}
-        >
-          <h3 id={category.category.id}>{category.category.name}</h3>
-        </Link>
-      )
-    })
+    return this.preparedExamples.map((category, i) => (
+      <Link to={'/examples/' + category.category.id} key={category.category.id}>
+        <h3 id={category.category.id}>{category.category.name}</h3>
+      </Link>
+    ))
   }
 
   render() {
diff --git a/lib/analytics.js b/lib/analytics.js
index 3f4c88c68f4883f1785a1e7c74a54c5b6d0991c2..94799f93119f130c4a894f322c6b345e804e2d53 100644
--- a/lib/analytics.js
+++ b/lib/analytics.js
@@ -62,7 +62,7 @@ function defaultAnalytics() {
 function load() {
   const defaultAnalyticsObject = defaultAnalytics()
   if (useRedis) {
-    redis.get(analyticsPath, function(err, value) {
+    redis.get(analyticsPath, (err, value) => {
       if (err == null && value != null) {
         // if/try/return trick:
         // if error, then the rest of the function is run.
diff --git a/lib/github-auth.js b/lib/github-auth.js
index 2c4b456c92c4b966fcfff792d371f44a8af0c68a..bde3076f4a4fa6ae0841c2cef49a2ca6b581139a 100644
--- a/lib/github-auth.js
+++ b/lib/github-auth.js
@@ -25,7 +25,7 @@ if (serverSecrets && serverSecrets.gh_token) {
 function setRoutes(server) {
   const baseUrl = process.env.BASE_URL || 'https://img.shields.io'
 
-  server.route(/^\/github-auth$/, function(data, match, end, ask) {
+  server.route(/^\/github-auth$/, (data, match, end, ask) => {
     if (!(serverSecrets && serverSecrets.gh_client_id)) {
       return end('This server is missing GitHub client secrets.')
     }
@@ -41,7 +41,7 @@ function setRoutes(server) {
     end('')
   })
 
-  server.route(/^\/github-auth\/done$/, function(data, match, end, ask) {
+  server.route(/^\/github-auth\/done$/, (data, match, end, ask) => {
     if (
       !(
         serverSecrets &&
@@ -68,7 +68,7 @@ function setRoutes(server) {
       }),
       method: 'POST',
     }
-    request(options, function(err, res, body) {
+    request(options, (err, res, body) => {
       if (err != null) {
         return end('The connection to GitHub failed.')
       }
@@ -96,16 +96,16 @@ function setRoutes(server) {
           '<p><a href="/">Back to the website</a></p>'
       )
 
-      sendTokenToAllServers(token).catch(function(e) {
+      sendTokenToAllServers(token).catch(e => {
         console.error('GitHub user token transmission failed:', e)
       })
     })
   })
 
-  server.route(/^\/github-auth\/add-token$/, function(data, match, end, ask) {
+  server.route(/^\/github-auth\/add-token$/, (data, match, end, ask) => {
     if (!secretIsValid(data.shieldsSecret)) {
       // An unknown entity tries to connect. Let the connection linger for 10s.
-      return setTimeout(function() {
+      return setTimeout(() => {
         end('Invalid secret.')
       }, 10000)
     }
@@ -117,33 +117,34 @@ function setRoutes(server) {
 function sendTokenToAllServers(token) {
   const ips = serverSecrets.shieldsIps
   return Promise.all(
-    ips.map(function(ip) {
-      return new Promise(function(resolve, reject) {
-        const options = {
-          url: 'https://' + ip + '/github-auth/add-token',
-          method: 'POST',
-          form: {
-            shieldsSecret: serverSecrets.shieldsSecret,
-            token: token,
-          },
-          // We target servers by IP, and we use HTTPS. Assuming that
-          // 1. Internet routers aren't hacked, and
-          // 2. We don't unknowingly lose our IP to someone else,
-          // we're not leaking people's and our information.
-          // (If we did, it would have no impact, as we only ask for a token,
-          // no GitHub scope. The malicious entity would only be able to use
-          // our rate limit pool.)
-          // FIXME: use letsencrypt.
-          strictSSL: false,
-        }
-        request(options, function(err, res, body) {
-          if (err != null) {
-            return reject(err)
+    ips.map(
+      ip =>
+        new Promise((resolve, reject) => {
+          const options = {
+            url: 'https://' + ip + '/github-auth/add-token',
+            method: 'POST',
+            form: {
+              shieldsSecret: serverSecrets.shieldsSecret,
+              token: token,
+            },
+            // We target servers by IP, and we use HTTPS. Assuming that
+            // 1. Internet routers aren't hacked, and
+            // 2. We don't unknowingly lose our IP to someone else,
+            // we're not leaking people's and our information.
+            // (If we did, it would have no impact, as we only ask for a token,
+            // no GitHub scope. The malicious entity would only be able to use
+            // our rate limit pool.)
+            // FIXME: use letsencrypt.
+            strictSSL: false,
           }
-          resolve()
+          request(options, (err, res, body) => {
+            if (err != null) {
+              return reject(err)
+            }
+            resolve()
+          })
         })
-      })
-    })
+    )
   )
 }
 
@@ -179,9 +180,7 @@ function isTokenUsable(token, now) {
 // with a reasonable chance that the request will succeed.
 function usableTokens() {
   const now = utcEpochSeconds()
-  return githubUserTokens.filter(function(token) {
-    return isTokenUsable(token, now)
-  })
+  return githubUserTokens.filter(token => isTokenUsable(token, now))
 }
 
 // Retrieve a user token if there is one for which we believe there are requests
@@ -308,7 +307,7 @@ function githubRequest(request, url, query, cb) {
     url += '?' + qs
   }
 
-  request(url, { headers: headers }, function(err, res, buffer) {
+  request(url, { headers: headers }, (err, res, buffer) => {
     if (globalToken !== null && githubToken !== null && err === null) {
       if (res.statusCode === 401) {
         // Unauthorized.
diff --git a/lib/github-provider.js b/lib/github-provider.js
index b077f797d0ead8257c620629c4523881f0232caa..28b106c0ed026dc310f657c3a5f463716d152666 100644
--- a/lib/github-provider.js
+++ b/lib/github-provider.js
@@ -14,7 +14,7 @@ const { age } = require('./color-formatters')
 function mapGithubCommitsSince({ camp, cache }, githubApiProvider) {
   camp.route(
     /^\/github\/commits-since\/([^/]+)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const user = match[1] // eg, SubtitleEdit
       const repo = match[2] // eg, subtitleedit
       const version = match[3] // eg, 3.4.7 or latest
@@ -73,7 +73,7 @@ function mapGithubCommitsSince({ camp, cache }, githubApiProvider) {
 function mapGithubReleaseDate({ camp, cache }, githubApiProvider) {
   camp.route(
     /^\/github\/(release-date|release-date-pre)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const releaseType = match[1] // eg, release-date-pre / release-date
       const user = match[2] // eg, microsoft
       const repo = match[3] // eg, vscode
diff --git a/lib/licenses.spec.js b/lib/licenses.spec.js
index 8ac6910912257f650af1730cef4cd92a95c91245..c176d76cb9c1592135910c4795e46741ac2b82c5 100644
--- a/lib/licenses.spec.js
+++ b/lib/licenses.spec.js
@@ -3,7 +3,7 @@
 const { test, given } = require('sazerac')
 const { licenseToColor } = require('./licenses')
 
-describe('license helpers', () => {
+describe('license helpers', function() {
   test(licenseToColor, () => {
     given('MIT').expect('green')
     given('MPL-2.0').expect('orange')
diff --git a/lib/load-logos.js b/lib/load-logos.js
index e9c2ec049ff392c0006fbd5ea45621a3f21f7dfc..b44eb4c5f543eb8bd0b84424a58bfa422d97415a 100644
--- a/lib/load-logos.js
+++ b/lib/load-logos.js
@@ -9,7 +9,7 @@ function loadLogos() {
   const logos = {}
   const logoDir = path.join(__dirname, '..', 'logo')
   const logoFiles = fs.readdirSync(logoDir)
-  logoFiles.forEach(function(filename) {
+  logoFiles.forEach(filename => {
     if (filename[0] === '.') {
       return
     }
diff --git a/lib/load-simple-icons.js b/lib/load-simple-icons.js
index f7f8d8f17df4ca0a118f154c617381fb3d920047..02729d42c92dac276cd3deffcfded225f750c2cc 100644
--- a/lib/load-simple-icons.js
+++ b/lib/load-simple-icons.js
@@ -4,7 +4,7 @@ const simpleIcons = require('simple-icons')
 const { svg2base64 } = require('./logo-helper')
 
 function loadSimpleIcons() {
-  Object.keys(simpleIcons).forEach(function(key) {
+  Object.keys(simpleIcons).forEach(key => {
     const k = key.toLowerCase().replace(/ /g, '-')
     if (k !== key) {
       simpleIcons[k] = simpleIcons[key]
diff --git a/lib/log.js b/lib/log.js
index 2f0cd06981fa144b36562fcda276cc31223d87e8..46fa4ee5b38bf37976c77ea4c68b6f5a92a6dde4 100644
--- a/lib/log.js
+++ b/lib/log.js
@@ -37,7 +37,7 @@ const throttledConsoleError = throttle(console.error, 10000, {
 module.exports.error = function error(...msg) {
   const d = date()
   listeners.forEach(f => f(d, ...msg))
-  Raven.captureException(msg, function(sendErr) {
+  Raven.captureException(msg, sendErr => {
     if (sendErr) {
       throttledConsoleError(
         'Failed to send captured exception to Sentry',
diff --git a/lib/log.spec.js b/lib/log.spec.js
index a5804be0b0353fc6ea8aa295bbf43786141d0787..0b4fc79589e3a603c6485dc8fd4c89527e6a0f52 100644
--- a/lib/log.spec.js
+++ b/lib/log.spec.js
@@ -12,27 +12,25 @@ function requireUncached(module) {
   return require(module)
 }
 
-describe('log', () => {
-  describe('error', () => {
-    before(() => {
+describe('log', function() {
+  describe('error', function() {
+    before(function() {
       this.clock = sinon.useFakeTimers()
-      sinon
-        .stub(Raven, 'captureException')
-        .callsFake(function fakeFn(e, callback) {
-          callback(new Error(`Cannot send message "${e}" to Sentry.`))
-        })
+      sinon.stub(Raven, 'captureException').callsFake((e, callback) => {
+        callback(new Error(`Cannot send message "${e}" to Sentry.`))
+      })
       // we have to create a spy before requiring 'error' function
       sinon.spy(console, 'error')
       this.error = requireUncached('./log').error
     })
 
-    after(() => {
+    after(function() {
       this.clock.restore()
       console.error.restore()
       Raven.captureException.restore()
     })
 
-    it('should throttle errors from Raven client', () => {
+    it('should throttle errors from Raven client', function() {
       this.error('test error 1')
       this.error('test error 2')
       this.error('test error 3')
diff --git a/lib/luarocks-version.js b/lib/luarocks-version.js
index d2b73d84d2d42309136ad06cda448e74dc2671d9..70df1ad2aca0cf55caf27bc68ad1e46e369027e0 100644
--- a/lib/luarocks-version.js
+++ b/lib/luarocks-version.js
@@ -30,7 +30,7 @@ exports.compareVersionLists = compareVersionLists
 function parseVersion(versionString) {
   versionString = versionString.toLowerCase().replace('-', '.')
   const versionList = []
-  versionString.split('.').forEach(function(versionPart) {
+  versionString.split('.').forEach(versionPart => {
     const parsedPart = /(\d*)([a-z]*)(\d*)/.exec(versionPart)
     if (parsedPart[1]) {
       versionList.push(parseInt(parsedPart[1]))
diff --git a/lib/make-badge.js b/lib/make-badge.js
index 03ca3498f784b0cc032247c96cc603a2f33200b4..656b4341e0e9a22c4894ffb9917f28e45dc7705b 100644
--- a/lib/make-badge.js
+++ b/lib/make-badge.js
@@ -14,7 +14,7 @@ const badgeKeyWidthCache = new LruCache(1000)
 const templates = {}
 const templateFiles = fs.readdirSync(path.join(__dirname, '..', 'templates'))
 dot.templateSettings.strip = false // Do not strip whitespace.
-templateFiles.forEach(async function(filename) {
+templateFiles.forEach(async filename => {
   if (filename[0] === '.') {
     return
   }
@@ -29,7 +29,7 @@ templateFiles.forEach(async function(filename) {
     // Substitute dot code.
     const mapping = new Map()
     let mappingIndex = 1
-    const untemplatedSvg = templateData.replace(/{{.*?}}/g, function(match) {
+    const untemplatedSvg = templateData.replace(/{{.*?}}/g, match => {
       // Weird substitution that currently works for all templates.
       const mapKey = '99999990' + mappingIndex + '.1'
       mappingIndex++
@@ -52,9 +52,9 @@ templateFiles.forEach(async function(filename) {
     // Substitute dot code back.
     let svg = data
     const unmappedKeys = []
-    mapping.forEach(function(value, key) {
+    mapping.forEach((value, key) => {
       let keySubstituted = false
-      svg = svg.replace(RegExp(key, 'g'), function() {
+      svg = svg.replace(RegExp(key, 'g'), () => {
         keySubstituted = true
         return value
       })
diff --git a/lib/make-badge.spec.js b/lib/make-badge.spec.js
index 658f0cb4a14d537de83242eab5d189f40a02e0ac..eaf4aacc0120c57363723f65a7d6e0a636e3dfc7 100644
--- a/lib/make-badge.spec.js
+++ b/lib/make-badge.spec.js
@@ -21,12 +21,12 @@ function testColor(color = '') {
   ).colorB
 }
 
-describe('The badge generator', () => {
-  beforeEach(() => {
+describe('The badge generator', function() {
+  beforeEach(function() {
     _badgeKeyWidthCache.clear()
   })
 
-  describe('color test', () => {
+  describe('color test', function() {
     test(testColor, () => {
       // valid hex
       given('#4c1').expect('#4c1')
@@ -68,8 +68,8 @@ describe('The badge generator', () => {
     })
   })
 
-  describe('SVG', () => {
-    it('should produce SVG', () => {
+  describe('SVG', function() {
+    it('should produce SVG', function() {
       const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
       expect(svg)
         .to.satisfy(isSvg)
@@ -77,24 +77,24 @@ describe('The badge generator', () => {
         .and.to.include('grown')
     })
 
-    it('should always produce the same SVG (unless we have changed something!)', () => {
+    it('should always produce the same SVG (unless we have changed something!)', function() {
       const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
       snapshot(svg)
     })
 
-    it('should cache width of badge key', () => {
+    it('should cache width of badge key', function() {
       makeBadge({ text: ['cached', 'not-cached'], format: 'svg' })
       expect(_badgeKeyWidthCache.cache).to.have.keys('cached')
     })
   })
 
-  describe('JSON', () => {
-    it('should always produce the same JSON (unless we have changed something!)', () => {
+  describe('JSON', function() {
+    it('should always produce the same JSON (unless we have changed something!)', function() {
       const json = makeBadge({ text: ['cactus', 'grown'], format: 'json' })
       snapshot(json)
     })
 
-    it('should replace unknown json template with "default"', () => {
+    it('should replace unknown json template with "default"', function() {
       const jsonBadgeWithUnknownStyle = makeBadge({
         text: ['name', 'Bob'],
         format: 'json',
@@ -112,7 +112,7 @@ describe('The badge generator', () => {
       })
     })
 
-    it('should replace unknown svg template with "flat"', () => {
+    it('should replace unknown svg template with "flat"', function() {
       const jsonBadgeWithUnknownStyle = makeBadge({
         text: ['name', 'Bob'],
         format: 'svg',
@@ -129,9 +129,9 @@ describe('The badge generator', () => {
     })
   })
 
-  describe('"for-the-badge" template badge generation', () => {
+  describe('"for-the-badge" template badge generation', function() {
     // https://github.com/badges/shields/issues/1280
-    it('numbers should produce a string', () => {
+    it('numbers should produce a string', function() {
       const svg = makeBadge({
         text: [1998, 1999],
         format: 'svg',
@@ -142,7 +142,7 @@ describe('The badge generator', () => {
         .and.to.include('1999')
     })
 
-    it('lowercase/mixedcase string should produce uppercase string', () => {
+    it('lowercase/mixedcase string should produce uppercase string', function() {
       const svg = makeBadge({
         text: ['Label', '1 string'],
         format: 'svg',
@@ -154,8 +154,8 @@ describe('The badge generator', () => {
     })
   })
 
-  describe('"social" template badge generation', () => {
-    it('should produce capitalized string for badge key', () => {
+  describe('"social" template badge generation', function() {
+    it('should produce capitalized string for badge key', function() {
       const svg = makeBadge({
         text: ['some-key', 'some-value'],
         format: 'svg',
@@ -167,7 +167,7 @@ describe('The badge generator', () => {
     })
 
     // https://github.com/badges/shields/issues/1606
-    it('should handle empty strings used as badge keys', () => {
+    it('should handle empty strings used as badge keys', function() {
       const svg = makeBadge({
         text: ['', 'some-value'],
         format: 'json',
@@ -178,8 +178,8 @@ describe('The badge generator', () => {
         .and.to.include('some-value')
     })
   })
-  describe('badges with logos should always produce the same badge', () => {
-    it('shields GitHub logo default color (#333333)', () => {
+  describe('badges with logos should always produce the same badge', function() {
+    it('shields GitHub logo default color (#333333)', function() {
       const svg = makeBadge({
         text: ['label', 'message'],
         format: 'svg',
@@ -188,7 +188,7 @@ describe('The badge generator', () => {
       snapshot(svg)
     })
 
-    it('shields GitHub logo custom color (whitesmoke)', () => {
+    it('shields GitHub logo custom color (whitesmoke)', function() {
       const svg = makeBadge({
         text: ['label', 'message'],
         format: 'svg',
@@ -198,7 +198,7 @@ describe('The badge generator', () => {
       snapshot(svg)
     })
 
-    it('simple-icons javascript logo default color (#F7DF1E)', () => {
+    it('simple-icons javascript logo default color (#F7DF1E)', function() {
       const svg = makeBadge({
         text: ['label', 'message'],
         format: 'svg',
@@ -207,7 +207,7 @@ describe('The badge generator', () => {
       snapshot(svg)
     })
 
-    it('simple-icons javascript logo custom color (rgba(46,204,113,0.8))', () => {
+    it('simple-icons javascript logo custom color (rgba(46,204,113,0.8))', function() {
       const svg = makeBadge({
         text: ['label', 'message'],
         format: 'svg',
diff --git a/lib/nuget-provider.js b/lib/nuget-provider.js
index 4879bd4d10e48db5802e7fcb2cab752a05a7918c..dd16bede54a08335dfdc03efaec4a631edd051c6 100644
--- a/lib/nuget-provider.js
+++ b/lib/nuget-provider.js
@@ -24,7 +24,7 @@ function mapNugetFeedv2({ camp, cache }, pattern, offset, getInfo) {
     request(
       reqUrl,
       { headers: { Accept: 'application/atom+json,application/json' } },
-      function(err, res, buffer) {
+      (err, res, buffer) => {
         if (err != null) {
           done(new Error('inaccessible'))
           return
@@ -51,14 +51,14 @@ function mapNugetFeedv2({ camp, cache }, pattern, offset, getInfo) {
 
   camp.route(
     vRegex,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const info = getInfo(match)
       const site = info.site // eg, `Chocolatey`, or `YoloDev`
       const repo = match[offset + 1] // eg, `Nuget.Core`.
       const format = match[offset + 2]
       const apiUrl = info.feed
       const badgeData = getBadgeData(site, data)
-      getNugetPackage(apiUrl, repo, null, request, function(err, data) {
+      getNugetPackage(apiUrl, repo, null, request, (err, data) => {
         if (err != null) {
           badgeData.text[1] = err.message
           sendBadge(format, badgeData)
@@ -80,14 +80,14 @@ function mapNugetFeedv2({ camp, cache }, pattern, offset, getInfo) {
 
   camp.route(
     vPreRegex,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const info = getInfo(match)
       const site = info.site // eg, `Chocolatey`, or `YoloDev`
       const repo = match[offset + 1] // eg, `Nuget.Core`.
       const format = match[offset + 2]
       const apiUrl = info.feed
       const badgeData = getBadgeData(site, data)
-      getNugetPackage(apiUrl, repo, true, request, function(err, data) {
+      getNugetPackage(apiUrl, repo, true, request, (err, data) => {
         if (err != null) {
           badgeData.text[1] = err.message
           sendBadge(format, badgeData)
@@ -109,13 +109,13 @@ function mapNugetFeedv2({ camp, cache }, pattern, offset, getInfo) {
 
   camp.route(
     dtRegex,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const info = getInfo(match)
       const repo = match[offset + 1] // eg, `Nuget.Core`.
       const format = match[offset + 2]
       const apiUrl = info.feed
       const badgeData = getBadgeData('downloads', data)
-      getNugetPackage(apiUrl, repo, null, request, function(err, data) {
+      getNugetPackage(apiUrl, repo, null, request, (err, data) => {
         if (err != null) {
           badgeData.text[1] = err.message
           sendBadge(format, badgeData)
@@ -206,7 +206,7 @@ function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) {
   }
 
   function getNugetVersion(apiUrl, id, includePre, request, done) {
-    getNugetData(apiUrl, id, request, function(err, data) {
+    getNugetData(apiUrl, id, request, (err, data) => {
       if (err) {
         done(err)
         return
@@ -214,9 +214,9 @@ function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) {
       let versions = data.versions || []
       if (!includePre) {
         // Remove prerelease versions.
-        const filteredVersions = versions.filter(function(version) {
-          return !/-/.test(version.version)
-        })
+        const filteredVersions = versions.filter(
+          version => !/-/.test(version.version)
+        )
         if (filteredVersions.length > 0) {
           versions = filteredVersions
         }
@@ -228,14 +228,14 @@ function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) {
 
   camp.route(
     vRegex,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const info = getInfo(match)
       const site = info.site // eg, `Chocolatey`, or `YoloDev`
       const repo = match[offset + 1] // eg, `Nuget.Core`.
       const format = match[offset + 2]
       const apiUrl = info.feed
       const badgeData = getBadgeData(site, data)
-      getNugetVersion(apiUrl, repo, false, request, function(err, version) {
+      getNugetVersion(apiUrl, repo, false, request, (err, version) => {
         if (err != null) {
           badgeData.text[1] = err.message
           sendBadge(format, badgeData)
@@ -261,14 +261,14 @@ function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) {
 
   camp.route(
     vPreRegex,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const info = getInfo(match)
       const site = info.site // eg, `Chocolatey`, or `YoloDev`
       const repo = match[offset + 1] // eg, `Nuget.Core`.
       const format = match[offset + 2]
       const apiUrl = info.feed
       const badgeData = getBadgeData(site, data)
-      getNugetVersion(apiUrl, repo, true, request, function(err, version) {
+      getNugetVersion(apiUrl, repo, true, request, (err, version) => {
         if (err != null) {
           badgeData.text[1] = err.message
           sendBadge(format, badgeData)
@@ -294,13 +294,13 @@ function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) {
 
   camp.route(
     dtRegex,
-    cache(function(data, match, sendBadge, request) {
+    cache((data, match, sendBadge, request) => {
       const info = getInfo(match)
       const repo = match[offset + 1] // eg, `Nuget.Core`.
       const format = match[offset + 2]
       const apiUrl = info.feed
       const badgeData = getBadgeData('downloads', data)
-      getNugetData(apiUrl, repo, request, function(err, nugetData) {
+      getNugetData(apiUrl, repo, request, (err, nugetData) => {
         if (err != null) {
           badgeData.text[1] = err.message
           sendBadge(format, badgeData)
diff --git a/lib/pypi-helpers.js b/lib/pypi-helpers.js
index b51d018883315a58fb21bb56f9fb270687976348..c89f003142db545ac5f2b9a264e06048212bec66 100644
--- a/lib/pypi-helpers.js
+++ b/lib/pypi-helpers.js
@@ -23,7 +23,7 @@ const parseDjangoVersionString = function(str) {
 
 // sort an array of django versions low to high
 const sortDjangoVersions = function(versions) {
-  return versions.sort(function(a, b) {
+  return versions.sort((a, b) => {
     if (
       parseDjangoVersionString(a).major === parseDjangoVersionString(b).major
     ) {
diff --git a/lib/pypi-helpers.spec.js b/lib/pypi-helpers.spec.js
index 1d62eeff972d6dc185cd48fd957473903690ad25..acc19bd56d6e4eca7cc5a52d1e17601d2a6ed56d 100644
--- a/lib/pypi-helpers.spec.js
+++ b/lib/pypi-helpers.spec.js
@@ -53,9 +53,7 @@ describe('PyPI helpers', function() {
 
     // regex that matches everything
     given(classifiersFixture, /^([\S\s+]+)$/).expect(
-      classifiersFixture.info.classifiers.map(function(e) {
-        return e.toLowerCase()
-      })
+      classifiersFixture.info.classifiers.map(e => e.toLowerCase())
     )
 
     // regex that matches nothing
diff --git a/lib/request-handler.js b/lib/request-handler.js
index bde110f2aedcdf038e335a2bc116f779e875d7c5..48d99ed5c95c63d435b148aca349f4be820de8f9 100644
--- a/lib/request-handler.js
+++ b/lib/request-handler.js
@@ -211,6 +211,7 @@ function handleRequest(makeBadge, handlerOptions) {
       const result = handlerOptions.handler(
         filteredQueryParams,
         match,
+        // eslint-disable-next-line mocha/prefer-arrow-callback
         function sendBadge(format, badgeData) {
           if (serverUnresponsive) {
             return
diff --git a/lib/suggest.js b/lib/suggest.js
index 937a156acc27be688db6dbe6defba96089827563..c262c02e4b6a1bbfe0be5e693031690f7ba003a9 100644
--- a/lib/suggest.js
+++ b/lib/suggest.js
@@ -51,15 +51,13 @@ function findSuggestions(githubApiProvider, url, cb) {
   }
   promises.push(twitterPage(url))
   Promise.all(promises)
-    .then(function(badges) {
+    .then(badges => {
       // eslint-disable-next-line standard/no-callback-literal
       cb({
-        badges: badges.filter(function(b) {
-          return b != null
-        }),
+        badges: badges.filter(b => b != null),
       })
     })
-    .catch(function(err) {
+    .catch(err => {
       // eslint-disable-next-line standard/no-callback-literal
       cb({ badges: [], err: err })
     })
@@ -117,7 +115,7 @@ function githubStars(user, repo) {
 function githubLicense(githubApiProvider, user, repo) {
   return new Promise(resolve => {
     const apiUrl = `/repos/${user}/${repo}/license`
-    githubApiProvider.request(request, apiUrl, {}, function(err, res, buffer) {
+    githubApiProvider.request(request, apiUrl, {}, (err, res, buffer) => {
       if (err !== null) {
         resolve(null)
         return
diff --git a/lib/svg-badge-parser.js b/lib/svg-badge-parser.js
index bd26d9f2293513afe04aeea2069c68f3f83bfe69..144d52e4c953e8639029348ba5a8efb293741fd7 100644
--- a/lib/svg-badge-parser.js
+++ b/lib/svg-badge-parser.js
@@ -21,7 +21,7 @@ function valueFromSvgBadge(svg) {
 // Get data from a svg-style badge.
 // cb: function(err, string)
 function fetchFromSvg(request, url, cb) {
-  request(url, function(err, res, buffer) {
+  request(url, (err, res, buffer) => {
     if (err !== null) {
       cb(err)
     } else {
diff --git a/lib/sys/monitor.js b/lib/sys/monitor.js
index 0ff815e17eb53a6daf3406bdac52944f1973ec16..0f0050991c231bea94c88f93e02834fec7227079 100644
--- a/lib/sys/monitor.js
+++ b/lib/sys/monitor.js
@@ -9,7 +9,7 @@ const log = require('../log')
 function secretInvalid(req, res) {
   if (!secretIsValid(req.password)) {
     // An unknown entity tries to connect. Let the connection linger for a minute.
-    setTimeout(function() {
+    setTimeout(() => {
       res.json({ errors: [{ code: 'invalid_secrets' }] })
     }, 10000)
     return true
@@ -27,7 +27,7 @@ function setRoutes(server) {
     whitelist: /^https?:\/\/shields\.io\/$/,
   })
 
-  server.handle(function monitorHandler(req, res, next) {
+  server.handle((req, res, next) => {
     if (req.url.startsWith('/sys/')) {
       if (secretInvalid(req, res)) {
         return
diff --git a/lib/teamcity-badge-helpers.js b/lib/teamcity-badge-helpers.js
index 3ee34077302c1935c3ab0fa2751ae1db4f5e3358..24ace473fe4cd313228feb331bd5494ba8c2ffc7 100644
--- a/lib/teamcity-badge-helpers.js
+++ b/lib/teamcity-badge-helpers.js
@@ -13,33 +13,37 @@ function teamcityBadge(
 ) {
   const apiUrl = url + '/app/rest/builds/buildType:(id:' + buildId + ')?guest=1'
   const badgeData = getBadgeData('build', data)
-  request(apiUrl, { headers: { Accept: 'application/json' } }, function(
-    err,
-    res,
-    buffer
-  ) {
-    if (err != null) {
-      badgeData.text[1] = 'inaccessible'
-      sendBadge(format, badgeData)
-      return
-    }
-    try {
-      const data = JSON.parse(buffer)
-      if (advanced)
-        badgeData.text[1] = (data.statusText || data.status || '').toLowerCase()
-      else badgeData.text[1] = (data.status || '').toLowerCase()
-      if (data.status === 'SUCCESS') {
-        badgeData.colorscheme = 'brightgreen'
-        badgeData.text[1] = 'passing'
-      } else {
-        badgeData.colorscheme = 'red'
+  request(
+    apiUrl,
+    { headers: { Accept: 'application/json' } },
+    (err, res, buffer) => {
+      if (err != null) {
+        badgeData.text[1] = 'inaccessible'
+        sendBadge(format, badgeData)
+        return
+      }
+      try {
+        const data = JSON.parse(buffer)
+        if (advanced)
+          badgeData.text[1] = (
+            data.statusText ||
+            data.status ||
+            ''
+          ).toLowerCase()
+        else badgeData.text[1] = (data.status || '').toLowerCase()
+        if (data.status === 'SUCCESS') {
+          badgeData.colorscheme = 'brightgreen'
+          badgeData.text[1] = 'passing'
+        } else {
+          badgeData.colorscheme = 'red'
+        }
+        sendBadge(format, badgeData)
+      } catch (e) {
+        badgeData.text[1] = 'invalid'
+        sendBadge(format, badgeData)
       }
-      sendBadge(format, badgeData)
-    } catch (e) {
-      badgeData.text[1] = 'invalid'
-      sendBadge(format, badgeData)
     }
-  })
+  )
 }
 
 module.exports = {
diff --git a/lib/text-formatters.js b/lib/text-formatters.js
index e38f71bf918d9c975fee9721086f6f773be77af8..dff54b3a22da0bdabe42a32dc062329113d7a0da 100644
--- a/lib/text-formatters.js
+++ b/lib/text-formatters.js
@@ -50,9 +50,7 @@ function ordinalNumber(n) {
 // Given a number, string with appropriate unit in the metric system, SI.
 // Note: numbers beyond the peta- cannot be represented as integers in JS.
 const metricPrefix = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
-const metricPower = metricPrefix.map(function(a, i) {
-  return Math.pow(1000, i + 1)
-})
+const metricPower = metricPrefix.map((a, i) => Math.pow(1000, i + 1))
 function metric(n) {
   for (let i = metricPrefix.length - 1; i >= 0; i--) {
     const limit = metricPower[i]
diff --git a/lib/text-measurer.spec.js b/lib/text-measurer.spec.js
index 1ea58ea22868abc977090eeddca2756e7685a61c..2ea684aef9d727f2694f59d52f22af6c50927758 100644
--- a/lib/text-measurer.spec.js
+++ b/lib/text-measurer.spec.js
@@ -63,7 +63,7 @@ function registerTests(fontPath, skip) {
         '[prismic "1.1"]',
       ]
 
-      strings.forEach(function(str) {
+      strings.forEach(str => {
         it(`should measure '${str}' in parity with PDFKit`, function() {
           if (skip) {
             this.skip()
@@ -75,7 +75,7 @@ function registerTests(fontPath, skip) {
         })
       })
 
-      strings.forEach(function(str) {
+      strings.forEach(str => {
         it(`should measure '${str}' without invoking PDFKit`, function() {
           if (skip) {
             this.skip()
@@ -122,7 +122,7 @@ function registerTests(fontPath, skip) {
     context('when given non-ASCII strings', function() {
       const strings = [starRating(3.5), '\u2026']
 
-      strings.forEach(function(str) {
+      strings.forEach(str => {
         it(`should measure '${str}' in parity with PDFKit`, function() {
           if (skip) {
             this.skip()
@@ -134,7 +134,7 @@ function registerTests(fontPath, skip) {
         })
       })
 
-      strings.forEach(function(str) {
+      strings.forEach(str => {
         it(`should invoke the base when measuring '${str}'`, function() {
           if (skip) {
             this.skip()
diff --git a/lib/version.js b/lib/version.js
index 65f303b27f97792c6eceb72cadbe3fe7e0a13a67..3f993c311afad1e11716510cdfeb87e9f02f9dd9 100644
--- a/lib/version.js
+++ b/lib/version.js
@@ -15,30 +15,24 @@ function latest(versions, { pre = false } = {}) {
   let version = ''
   let origVersions = versions
   // return all results that are likely semver compatible versions
-  versions = origVersions.filter(function(version) {
-    return /\d+\.\d+/.test(version)
-  })
+  versions = origVersions.filter(version => /\d+\.\d+/.test(version))
   // If no semver versions then look for single numbered versions
   if (!versions.length) {
-    versions = origVersions.filter(function(version) {
-      return /\d+/.test(version)
-    })
+    versions = origVersions.filter(version => /\d+/.test(version))
   }
   if (!pre) {
     // remove pre-releases from array
-    versions = versions.filter(function(version) {
-      return !/\d+-\w+/.test(version)
-    })
+    versions = versions.filter(version => !/\d+-\w+/.test(version))
   }
   try {
-    version = versions.sort((a, b) => {
-      // coerce to string then lowercase otherwise alpha > RC
-      return semver.rcompare(
+    // coerce to string then lowercase otherwise alpha > RC
+    version = versions.sort((a, b) =>
+      semver.rcompare(
         ('' + a).toLowerCase(),
         ('' + b).toLowerCase(),
         /* loose */ true
       )
-    })[0]
+    )[0]
   } catch (e) {
     version = latestDottedVersion(versions)
   }
@@ -90,12 +84,8 @@ function compareDottedVersion(v1, v2) {
     const numbers2 = parts2[1]
     const distinguisher1 = parts1[2]
     const distinguisher2 = parts2[2]
-    const numlist1 = numbers1.split('.').map(function(e) {
-      return +e
-    })
-    const numlist2 = numbers2.split('.').map(function(e) {
-      return +e
-    })
+    const numlist1 = numbers1.split('.').map(e => +e)
+    const numlist2 = numbers2.split('.').map(e => +e)
     const cmp = listCompare(numlist1, numlist2)
     if (cmp !== 0) {
       return cmp
diff --git a/package-lock.json b/package-lock.json
index 8cfefb7e1baaa37b77ab289946d2494cb84b4809..edcaeb52cb141e8ea3a70b69e631d03d3308e980 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4979,6 +4979,15 @@
         }
       }
     },
+    "eslint-plugin-mocha": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-5.1.0.tgz",
+      "integrity": "sha512-fkmup5IRzCWaH+IVYymVKs3P8SO6Pf9oiuodwrM25YCuodi4UtLgc6z6j5vZeBwJMKj7F7gszqBPoILBP/pf+g==",
+      "dev": true,
+      "requires": {
+        "ramda": "^0.25.0"
+      }
+    },
     "eslint-plugin-node": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz",
diff --git a/package.json b/package.json
index b4fdd1853608b271a81849852f34efaa81c35b3c..a3795e3e833a3a7a3f25cc75e84a2a066b762bdc 100644
--- a/package.json
+++ b/package.json
@@ -128,6 +128,7 @@
     "eslint-config-standard-react": "^6.0.0",
     "eslint-plugin-chai-friendly": "^0.4.1",
     "eslint-plugin-import": "^2.8.0",
+    "eslint-plugin-mocha": "^5.1.0",
     "eslint-plugin-node": "^7.0.0",
     "eslint-plugin-promise": "^3.6.0",
     "eslint-plugin-react": "^7.6.1",
diff --git a/services/base-json.spec.js b/services/base-json.spec.js
index 5ad73a12d117a7f4b91b19c08eda04ab70815db8..fdbe7111b9805243ae6c09094401e23782912eda 100644
--- a/services/base-json.spec.js
+++ b/services/base-json.spec.js
@@ -26,7 +26,7 @@ class DummyJsonService extends BaseJsonService {
   }
 }
 
-describe('BaseJsonService', () => {
+describe('BaseJsonService', function() {
   it('handles unparseable json responses', async function() {
     const sendAndCacheRequest = async () => ({
       buffer: invalidJSON,
diff --git a/services/base.spec.js b/services/base.spec.js
index 0b7e8ef6dc808af5f639e6184374399b67d2e961..cdf75850ee80e411bc2cba304c075839dd320cf8 100644
--- a/services/base.spec.js
+++ b/services/base.spec.js
@@ -38,7 +38,7 @@ class DummyService extends BaseService {
   }
 }
 
-describe('BaseService', () => {
+describe('BaseService', function() {
   const defaultConfig = { handleInternalErrors: false }
 
   describe('URL pattern matching', function() {
@@ -131,7 +131,7 @@ describe('BaseService', () => {
     })
   })
 
-  describe.only('Error handling', function() {
+  describe('Error handling', function() {
     it('Handles internal errors', async function() {
       const serviceInstance = new DummyService(
         {},
@@ -265,7 +265,7 @@ describe('BaseService', () => {
     let mockCamp
     let mockHandleRequest
 
-    beforeEach(() => {
+    beforeEach(function() {
       mockCamp = {
         route: sinon.spy(),
       }
@@ -273,12 +273,12 @@ describe('BaseService', () => {
       DummyService.register(mockCamp, mockHandleRequest, defaultConfig)
     })
 
-    it('registers the service', () => {
+    it('registers the service', function() {
       expect(mockCamp.route).to.have.been.calledOnce
       expect(mockCamp.route).to.have.been.calledWith(expectedRouteRegex)
     })
 
-    it('handles the request', async () => {
+    it('handles the request', async function() {
       expect(mockHandleRequest).to.have.been.calledOnce
       const { handler: requestHandler } = mockHandleRequest.getCall(0).args[0]
 
diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js
index d8f5b4c6aa20a393f345e27dbf264591c77121ce..2c64a15262f9e21d6fd8d9d0b8a5b499bee1e37c 100644
--- a/services/gem/gem-downloads.service.js
+++ b/services/gem/gem-downloads.service.js
@@ -77,22 +77,14 @@ module.exports = class GemDownloads extends BaseJsonService {
       let versionData
       if (version !== null && version === 'stable') {
         const versions = json
-          .filter(function(ver) {
-            return ver.prerelease === false
-          })
-          .map(function(ver) {
-            return ver.number
-          })
+          .filter(ver => ver.prerelease === false)
+          .map(ver => ver.number)
         // Found latest stable version.
         const stableVersion = latestVersion(versions)
-        versionData = json.filter(function(ver) {
-          return ver.number === stableVersion
-        })[0]
+        versionData = json.filter(ver => ver.number === stableVersion)[0]
         downloads = versionData.downloads_count
       } else if (version !== null) {
-        versionData = json.filter(function(ver) {
-          return ver.number === version
-        })[0]
+        versionData = json.filter(ver => ver.number === version)[0]
 
         downloads = versionData.downloads_count
       } else {
diff --git a/services/gemnasium/gemnasium.tester.js b/services/gemnasium/gemnasium.tester.js
index 6f23ffa235e5d96460f191028151b9aaa2f9fb29..197627a759e748bff0e2ad4f557cb05029f4d7fc 100644
--- a/services/gemnasium/gemnasium.tester.js
+++ b/services/gemnasium/gemnasium.tester.js
@@ -10,7 +10,7 @@ module.exports = t
 
 t.create('no longer available (previously dependencies)')
   .get('/mathiasbynens/he.json')
-  .afterJSON(function(badge) {
+  .afterJSON(badge => {
     if (isDeprecated('gemnasium')) {
       expect(badge.name).to.equal('gemnasium')
       expect(badge.value).to.equal('no longer available')
diff --git a/services/github/auth/admin.js b/services/github/auth/admin.js
index 8ffbda32e5775dcf18638bd912bf23d13858e451..de13bc09f754563eff23ada7ff3d89f0995ad48b 100644
--- a/services/github/auth/admin.js
+++ b/services/github/auth/admin.js
@@ -19,7 +19,7 @@ function setRoutes(server) {
   server.ajax.on('github-auth/tokens', (json, end, ask) => {
     if (!secretIsValid(ask.password)) {
       // An unknown entity tries to connect. Let the connection linger for a minute.
-      return setTimeout(function() {
+      return setTimeout(() => {
         end('Invalid secret.')
       }, 10000)
     }
diff --git a/services/json/json.tester.js b/services/json/json.tester.js
index ad9378bb636b070a21837911c92ca4ba7fc50630..e6c39a87a3c7004c18a5060d50ec5ab75627e712 100644
--- a/services/json/json.tester.js
+++ b/services/json/json.tester.js
@@ -158,6 +158,6 @@ t.create('JSON from url | request should set Accept header')
       })
   )
   .expectJSON({ name: 'custom badge', value: 'test' })
-  .after(function() {
+  .after(() => {
     expect(headers).to.have.property('accept', 'application/json')
   })
diff --git a/services/service-tester.js b/services/service-tester.js
index f17d1970420657b6a00846f4c28f8b4fd546f975..db591535a1cab9da5b524315ef8de40d00ffbbd6 100644
--- a/services/service-tester.js
+++ b/services/service-tester.js
@@ -69,6 +69,7 @@ class ServiceTester {
     const specs = this.specs
 
     const fn = this._only ? describe.only : describe
+    // eslint-disable-next-line mocha/prefer-arrow-callback
     fn(this.title, function() {
       specs.forEach(spec => {
         spec.toss()
diff --git a/services/xml/xml.tester.js b/services/xml/xml.tester.js
index 9d00ea8ada56c37741713fa239149b8cb8290cdd..b3996f437d0b3a53860e9eb3426babba4ea46e0d 100644
--- a/services/xml/xml.tester.js
+++ b/services/xml/xml.tester.js
@@ -182,6 +182,6 @@ t.create('XML from url | request should set Accept header')
       })
   )
   .expectJSON({ name: 'custom badge', value: 'dynamic xml' })
-  .after(function() {
+  .after(() => {
     expect(headers).to.have.property('accept', 'application/xml, text/xml')
   })