Mailspring/build/tasks/nylaslint-task.coffee

154 lines
6.1 KiB
CoffeeScript
Raw Normal View History

path = require 'path'
fs = require 'fs-plus'
normalizeRequirePath = (requirePath, fPath) ->
if requirePath[0] is "."
return path.normalize(path.join(path.dirname(fPath), requirePath))
return requirePath
module.exports = (grunt) ->
grunt.registerMultiTask 'nylaslint', 'Check requires for file extensions compiled away', ->
done = @async()
extensionRegex = /require ['"].*\.(coffee|cjsx|jsx|es6|es)['"]/i
for fileset in @files
grunt.log.writeln('Nylinting ' + fileset.src.length + ' files.')
esExtensions = {
".es6": true
".es": true
".jsx": true
}
coffeeExtensions = {
".coffee": true
".cjsx": true
}
errors = []
esExport = {}
esNoExport = {}
esExportDefault = {}
# Temp TODO. Fix spec files
for f in fileset.src
continue if not esExtensions[path.extname(f)]
continue if not /-spec/.test(f)
content = fs.readFileSync(f, encoding:'utf8')
# https://regex101.com/r/rQ3eD0/1
# Matches only the first describe block
describeRe = /[\n]describe\(['"](.*?)['"], ?\(\) ?=> ?/m
if describeRe.test(content)
errors.push("#{f}: Spec has to start with function")
## NOTE: Comment me in if you want to fix these files.
# _str = require('underscore.string')
# replacer = (match, describeName, offset, string) ->
# fnName = _str.camelize(describeName, true)
# return "\ndescribe('#{describeName}', function #{fnName}() "
# newContent = content.replace(describeRe, replacer)
# fs.writeFileSync(f, newContent, encoding:'utf8')
# Build the list of ES6 files that export things and categorize
for f in fileset.src
continue if not esExtensions[path.extname(f)]
lookupPath = "#{path.dirname(f)}/#{path.basename(f, path.extname(f))}"
content = fs.readFileSync(f, encoding:'utf8')
if /module.exports\s?=\s?.+/gmi.test(content)
errors.push("#{f}: Don't use module.exports in ES6")
if /^export/gmi.test(content)
if /^export\ default/gmi.test(content)
esExportDefault[lookupPath] = true
else
esExport[lookupPath] = true
else
esNoExport[lookupPath] = true
# Now look again through all ES6 files, this time to check imports
# instead of exports.
for f in fileset.src
continue if not esExtensions[path.extname(f)]
content = fs.readFileSync(f, encoding:'utf8')
importRe = /import \{.*\} from ['"](.*?)['"]/gmi
while result = importRe.exec(content)
i = 1
while i < result.length
requirePath = result[i]
i += 1
lookupPath = normalizeRequirePath(requirePath, f)
if esExportDefault[lookupPath] or esNoExport[lookupPath]
errors.push("#{f}: Don't destructure default export #{requirePath}")
# Now look through all coffeescript files
# If they require things from ES6 files, ensure they're using the
# proper syntax.
for f in fileset.src
continue if esExtensions[path.extname(f)]
content = fs.readFileSync(f, encoding:'utf8')
if extensionRegex.test(content)
errors.push("#{f}: Remove extensions when requiring files")
requireRe = /require[ (]['"]([\w_./-]*?)['"]/gmi
while result = requireRe.exec(content)
i = 1
while i < result.length
requirePath = result[i]
i += 1
lookupPath = normalizeRequirePath(requirePath, f)
baseRequirePath = path.basename(requirePath)
plainRequireRe = new RegExp("require[ (]['\"].*#{baseRequirePath}['\"]\\)?$","gm")
defaultRequireRe = new RegExp("require\\(['\"].*#{baseRequirePath}['\"]\\)\\.default","gm")
if esExport[lookupPath]
if not plainRequireRe.test(content)
errors.push("#{f}: No `default` exported #{requirePath}")
else if esNoExport[lookupPath]
errors.push("#{f}: Nothing exported from #{requirePath}")
else if esExportDefault[lookupPath]
if not defaultRequireRe.test(content)
errors.push("#{f}: Add `default` to require #{requirePath}")
else
# must be a coffeescript or core file
if defaultRequireRe.test(content)
errors.push("#{f}: Don't ask for `default` from #{requirePath}")
if errors.length > 0
grunt.log.error(err) for err in errors
error = """
Please fix the #{errors.length} linter errors above. These are the issues we're looking for:
ISSUES WITH COFFEESCRIPT FILES:
1. Remove extensions when requiring files:
Since we compile files in production to plain `.js` files it's very important you do NOT include the file extension when `require`ing a file.
2. Add `default` to require:
As of Babel 6, `require` no longer returns whatever the `default` value is. If you are `require`ing an es6 file from a coffeescript file, you must explicitly request the `default` property. For example: do `require('./my-es6-file').default`
3. Don't ask for `default`:
If you're requiring a coffeescript file from a coffeescript file, you will almost never need to load a `default` object. This is likely an indication you incorrectly thought you were importing an ES6 file.
ISSUES WITH ES6 FILES:
4. Don't use module.exports in ES6:
You sholudn't manually assign module.exports anymore. Use proper ES6 module syntax like `export default` or `export const FOO`.
5. Don't destructure default export:
If you're using `import {FOO} from './bar'` in ES6 files, it's important that `./bar` does NOT export a `default`. Instead, in './bar', do `export const FOO = 'foo'`
6. Spec has to start with function
Top-level `describe` blocks can no longer use the `() => {}` function syntax. This will incorrectly bind `this` to the `window` object instead of the jasmine object. The top-level `describe` block must use the `function describeName() {}` syntax
"""
done(new Error(error))
done(null)