2015-07-20 01:56:24 +08:00
path = require ' path '
fs = require ' fs-plus '
2016-05-05 03:15:30 +08:00
normalizeRequirePath = (requirePath, fPath) ->
if requirePath [ 0 ] is " . "
return path . normalize ( path . join ( path . dirname ( fPath ) , requirePath ) )
return requirePath
2015-07-20 01:56:24 +08:00
module.exports = (grunt) ->
grunt . registerMultiTask ' nylaslint ' , ' Check requires for file extensions compiled away ' , ->
done = @ async ( )
2016-05-04 06:42:13 +08:00
extensionRegex = /require ['"].*\.(coffee|cjsx|jsx|es6|es)['"]/i
2015-07-20 01:56:24 +08:00
for fileset in @ files
grunt . log . writeln ( ' Nylinting ' + fileset . src . length + ' files. ' )
2016-05-04 06:42:13 +08:00
esExtensions = {
" .es6 " : true
" .es " : true
" .jsx " : true
}
coffeeExtensions = {
" .coffee " : true
" .cjsx " : true
}
2016-05-04 08:24:11 +08:00
errors = [ ]
esExport = { }
esNoExport = { }
esExportDefault = { }
2016-05-05 05:00:33 +08:00
# 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')
2016-05-05 03:15:30 +08:00
# Build the list of ES6 files that export things and categorize
2016-05-04 06:42:13 +08:00
for f in fileset . src
2016-05-04 08:24:11 +08:00
continue if not esExtensions [ path . extname ( f ) ]
2016-05-04 08:43:40 +08:00
lookupPath = " #{ path . dirname ( f ) } / #{ path . basename ( f , path . extname ( f ) ) } "
2016-05-04 06:42:13 +08:00
2016-05-04 08:24:11 +08:00
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 )
2016-05-04 08:43:40 +08:00
esExportDefault [ lookupPath ] = true
2016-05-04 08:24:11 +08:00
else
2016-05-04 08:43:40 +08:00
esExport [ lookupPath ] = true
2016-05-04 08:24:11 +08:00
else
2016-05-04 08:43:40 +08:00
esNoExport [ lookupPath ] = true
2016-05-04 08:24:11 +08:00
2016-05-05 03:15:30 +08:00
# 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 } " )
2016-05-04 06:42:13 +08:00
2016-05-05 03:15:30 +08:00
# Now look through all coffeescript files
# If they require things from ES6 files, ensure they're using the
# proper syntax.
2015-07-20 01:56:24 +08:00
for f in fileset . src
2016-05-04 08:24:11 +08:00
continue if esExtensions [ path . extname ( f ) ]
content = fs . readFileSync ( f , encoding : ' utf8 ' )
if extensionRegex . test ( content )
2016-05-05 03:15:30 +08:00
errors . push ( " #{ f } : Remove extensions when requiring files " )
2016-05-04 08:24:11 +08:00
requireRe = /require[ (]['"]([\w_./-]*?)['"]/gmi
while result = requireRe . exec ( content )
i = 1
while i < result . length
requirePath = result [ i ]
i += 1
2016-05-04 08:43:40 +08:00
2016-05-05 03:15:30 +08:00
lookupPath = normalizeRequirePath ( requirePath , f )
2016-05-04 08:43:40 +08:00
2016-05-04 08:24:11 +08:00
baseRequirePath = path . basename ( requirePath )
plainRequireRe = new RegExp ( " require[ (][ ' \" ].* #{ baseRequirePath } [ ' \" ] \\ )?$ " , " gm " )
defaultRequireRe = new RegExp ( " require \\ ([ ' \" ].* #{ baseRequirePath } [ ' \" ] \\ ) \\ .default " , " gm " )
2016-05-04 08:43:40 +08:00
if esExport [ lookupPath ]
2016-05-04 08:24:11 +08:00
if not plainRequireRe . test ( content )
2016-05-05 03:15:30 +08:00
errors . push ( " #{ f } : No `default` exported #{ requirePath } " )
2016-05-04 08:24:11 +08:00
2016-05-04 08:43:40 +08:00
else if esNoExport [ lookupPath ]
2016-05-05 03:15:30 +08:00
errors . push ( " #{ f } : Nothing exported from #{ requirePath } " )
2016-05-04 08:24:11 +08:00
2016-05-04 08:43:40 +08:00
else if esExportDefault [ lookupPath ]
2016-05-04 08:24:11 +08:00
if not defaultRequireRe . test ( content )
2016-05-05 03:15:30 +08:00
errors . push ( " #{ f } : Add `default` to require #{ requirePath } " )
2016-05-04 06:42:13 +08:00
2016-05-04 08:43:40 +08:00
else
# must be a coffeescript or core file
if defaultRequireRe . test ( content )
2016-05-05 03:15:30 +08:00
errors . push ( " #{ f } : Don ' t ask for `default` from #{ requirePath } " )
2016-05-04 08:43:40 +08:00
2016-05-04 06:42:13 +08:00
if errors . length > 0
grunt . log . error ( err ) for err in errors
2016-05-05 03:15:30 +08:00
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 ' `
2016-05-05 05:00:33 +08:00
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
2016-05-05 03:15:30 +08:00
"""
done ( new Error ( error ) )
2015-07-20 01:56:24 +08:00
2015-08-29 02:12:53 +08:00
done ( null )