mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-11 18:32:20 +08:00
190 lines
7 KiB
JavaScript
190 lines
7 KiB
JavaScript
|
/* eslint no-cond-assign: 0 */
|
||
|
const path = require('path');
|
||
|
const fs = require('fs-plus');
|
||
|
|
||
|
function normalizeRequirePath(requirePath, fPath) {
|
||
|
if (requirePath[0] === ".") {
|
||
|
return path.normalize(path.join(path.dirname(fPath), requirePath));
|
||
|
}
|
||
|
return requirePath;
|
||
|
}
|
||
|
|
||
|
|
||
|
module.exports = (grunt) => {
|
||
|
grunt.config.merge({
|
||
|
nylaslint: {
|
||
|
src: grunt.config('source:coffeescript').concat(grunt.config('source:es6')),
|
||
|
},
|
||
|
});
|
||
|
|
||
|
grunt.registerMultiTask('nylaslint', 'Check requires for file extensions compiled away', function nylaslint() {
|
||
|
const done = this.async();
|
||
|
|
||
|
// Enable once path errors are fixed.
|
||
|
if (process.platform === 'win32') {
|
||
|
done();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const extensionRegex = /require ['"].*\.(coffee|cjsx|jsx|es6|es)['"]/i;
|
||
|
|
||
|
for (const fileset of this.files) {
|
||
|
grunt.log.writeln(`Nylinting ${fileset.src.length} files.`);
|
||
|
|
||
|
const esExtensions = {
|
||
|
".es6": true,
|
||
|
".es": true,
|
||
|
".jsx": true,
|
||
|
};
|
||
|
|
||
|
const errors = [];
|
||
|
|
||
|
const esExport = {};
|
||
|
const esNoExport = {};
|
||
|
const esExportDefault = {};
|
||
|
|
||
|
// Temp TODO. Fix spec files
|
||
|
for (const f of fileset.src) {
|
||
|
if (!esExtensions[path.extname(f)]) { continue; }
|
||
|
if (!/-spec/.test(f)) { continue; }
|
||
|
|
||
|
const content = fs.readFileSync(f, {encoding: 'utf8'});
|
||
|
|
||
|
// https://regex101.com/r/rQ3eD0/1
|
||
|
// Matches only the first describe block
|
||
|
const 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) ->
|
||
|
// 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 (const f of fileset.src) {
|
||
|
if (!esExtensions[path.extname(f)]) { continue; }
|
||
|
const lookupPath = `${path.dirname(f)}/${path.basename(f, path.extname(f))}`;
|
||
|
const content = fs.readFileSync(f, {encoding: 'utf8'});
|
||
|
|
||
|
if (/module.exports\s?=\s?.+/gmi.test(content)) {
|
||
|
if (!f.endsWith('nylas-exports.es6')) {
|
||
|
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 (const f of fileset.src) {
|
||
|
let result = null;
|
||
|
if (!esExtensions[path.extname(f)]) {
|
||
|
continue;
|
||
|
}
|
||
|
const content = fs.readFileSync(f, {encoding: 'utf8'});
|
||
|
const importRe = /import \{.*\} from ['"](.*?)['"]/gmi;
|
||
|
|
||
|
while (result = importRe.exec(content)) {
|
||
|
for (const requirePath of result.slice(1)) {
|
||
|
const lookupPath = normalizeRequirePath(requirePath, f);
|
||
|
if (esExportDefault[lookupPath] || 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 (const f of fileset.src) {
|
||
|
let result = null;
|
||
|
if (esExtensions[path.extname(f)]) {
|
||
|
continue;
|
||
|
}
|
||
|
const content = fs.readFileSync(f, {encoding: 'utf8'});
|
||
|
if (extensionRegex.test(content)) {
|
||
|
errors.push(`${f}: Remove extensions when requiring files`);
|
||
|
}
|
||
|
|
||
|
const requireRe = /require[ (]['"]([\w_./-]*?)['"]/gmi;
|
||
|
|
||
|
while (result = requireRe.exec(content)) {
|
||
|
for (const requirePath of result.slice(1)) {
|
||
|
const lookupPath = normalizeRequirePath(requirePath, f);
|
||
|
|
||
|
const baseRequirePath = path.basename(requirePath);
|
||
|
|
||
|
const plainRequireRe = new RegExp(`require[ (]['"].*${baseRequirePath}['"]\\)?$`, "gm");
|
||
|
const defaultRequireRe = new RegExp(`require\\(['"].*${baseRequirePath}['"]\\)\\.default`, "gm");
|
||
|
|
||
|
if (esExport[lookupPath]) {
|
||
|
if (!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 (!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) {
|
||
|
for (const err of errors) { grunt.log.error(err); }
|
||
|
const 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);
|
||
|
});
|
||
|
}
|