229 lines
26 KiB
JavaScript
229 lines
26 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var _path = require('path');
|
||
|
|
||
|
var _path2 = _interopRequireDefault(_path);
|
||
|
|
||
|
var _fs = require('fs');
|
||
|
|
||
|
var _fs2 = _interopRequireDefault(_fs);
|
||
|
|
||
|
var _readPkgUp = require('read-pkg-up');
|
||
|
|
||
|
var _readPkgUp2 = _interopRequireDefault(_readPkgUp);
|
||
|
|
||
|
var _minimatch = require('minimatch');
|
||
|
|
||
|
var _minimatch2 = _interopRequireDefault(_minimatch);
|
||
|
|
||
|
var _resolve = require('eslint-module-utils/resolve');
|
||
|
|
||
|
var _resolve2 = _interopRequireDefault(_resolve);
|
||
|
|
||
|
var _importType = require('../core/importType');
|
||
|
|
||
|
var _importType2 = _interopRequireDefault(_importType);
|
||
|
|
||
|
var _staticRequire = require('../core/staticRequire');
|
||
|
|
||
|
var _staticRequire2 = _interopRequireDefault(_staticRequire);
|
||
|
|
||
|
var _docsUrl = require('../docsUrl');
|
||
|
|
||
|
var _docsUrl2 = _interopRequireDefault(_docsUrl);
|
||
|
|
||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
||
|
function hasKeys() {
|
||
|
let obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||
|
|
||
|
return Object.keys(obj).length > 0;
|
||
|
}
|
||
|
|
||
|
function arrayOrKeys(arrayOrObject) {
|
||
|
return Array.isArray(arrayOrObject) ? arrayOrObject : Object.keys(arrayOrObject);
|
||
|
}
|
||
|
|
||
|
function extractDepFields(pkg) {
|
||
|
return {
|
||
|
dependencies: pkg.dependencies || {},
|
||
|
devDependencies: pkg.devDependencies || {},
|
||
|
optionalDependencies: pkg.optionalDependencies || {},
|
||
|
peerDependencies: pkg.peerDependencies || {},
|
||
|
// BundledDeps should be in the form of an array, but object notation is also supported by
|
||
|
// `npm`, so we convert it to an array if it is an object
|
||
|
bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || [])
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function getDependencies(context, packageDir) {
|
||
|
let paths = [];
|
||
|
try {
|
||
|
const packageContent = {
|
||
|
dependencies: {},
|
||
|
devDependencies: {},
|
||
|
optionalDependencies: {},
|
||
|
peerDependencies: {},
|
||
|
bundledDependencies: []
|
||
|
};
|
||
|
|
||
|
if (packageDir && packageDir.length > 0) {
|
||
|
if (!Array.isArray(packageDir)) {
|
||
|
paths = [_path2.default.resolve(packageDir)];
|
||
|
} else {
|
||
|
paths = packageDir.map(dir => _path2.default.resolve(dir));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (paths.length > 0) {
|
||
|
// use rule config to find package.json
|
||
|
paths.forEach(dir => {
|
||
|
const _packageContent = extractDepFields(JSON.parse(_fs2.default.readFileSync(_path2.default.join(dir, 'package.json'), 'utf8')));
|
||
|
Object.keys(packageContent).forEach(depsKey => Object.assign(packageContent[depsKey], _packageContent[depsKey]));
|
||
|
});
|
||
|
} else {
|
||
|
// use closest package.json
|
||
|
Object.assign(packageContent, extractDepFields(_readPkgUp2.default.sync({ cwd: context.getFilename(), normalize: false }).pkg));
|
||
|
}
|
||
|
|
||
|
if (![packageContent.dependencies, packageContent.devDependencies, packageContent.optionalDependencies, packageContent.peerDependencies, packageContent.bundledDependencies].some(hasKeys)) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return packageContent;
|
||
|
} catch (e) {
|
||
|
if (paths.length > 0 && e.code === 'ENOENT') {
|
||
|
context.report({
|
||
|
message: 'The package.json file could not be found.',
|
||
|
loc: { line: 0, column: 0 }
|
||
|
});
|
||
|
}
|
||
|
if (e.name === 'JSONError' || e instanceof SyntaxError) {
|
||
|
context.report({
|
||
|
message: 'The package.json file could not be parsed: ' + e.message,
|
||
|
loc: { line: 0, column: 0 }
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function missingErrorMessage(packageName) {
|
||
|
return `'${packageName}' should be listed in the project's dependencies. ` + `Run 'npm i -S ${packageName}' to add it`;
|
||
|
}
|
||
|
|
||
|
function devDepErrorMessage(packageName) {
|
||
|
return `'${packageName}' should be listed in the project's dependencies, not devDependencies.`;
|
||
|
}
|
||
|
|
||
|
function optDepErrorMessage(packageName) {
|
||
|
return `'${packageName}' should be listed in the project's dependencies, ` + `not optionalDependencies.`;
|
||
|
}
|
||
|
|
||
|
function reportIfMissing(context, deps, depsOptions, node, name) {
|
||
|
// Do not report when importing types
|
||
|
if (node.importKind === 'type') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ((0, _importType2.default)(name, context) !== 'external') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const resolved = (0, _resolve2.default)(name, context);
|
||
|
if (!resolved) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const splitName = name.split('/');
|
||
|
const packageName = splitName[0][0] === '@' ? splitName.slice(0, 2).join('/') : splitName[0];
|
||
|
const isInDeps = deps.dependencies[packageName] !== undefined;
|
||
|
const isInDevDeps = deps.devDependencies[packageName] !== undefined;
|
||
|
const isInOptDeps = deps.optionalDependencies[packageName] !== undefined;
|
||
|
const isInPeerDeps = deps.peerDependencies[packageName] !== undefined;
|
||
|
const isInBundledDeps = deps.bundledDependencies.indexOf(packageName) !== -1;
|
||
|
|
||
|
if (isInDeps || depsOptions.allowDevDeps && isInDevDeps || depsOptions.allowPeerDeps && isInPeerDeps || depsOptions.allowOptDeps && isInOptDeps || depsOptions.allowBundledDeps && isInBundledDeps) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (isInDevDeps && !depsOptions.allowDevDeps) {
|
||
|
context.report(node, devDepErrorMessage(packageName));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (isInOptDeps && !depsOptions.allowOptDeps) {
|
||
|
context.report(node, optDepErrorMessage(packageName));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
context.report(node, missingErrorMessage(packageName));
|
||
|
}
|
||
|
|
||
|
function testConfig(config, filename) {
|
||
|
// Simplest configuration first, either a boolean or nothing.
|
||
|
if (typeof config === 'boolean' || typeof config === 'undefined') {
|
||
|
return config;
|
||
|
}
|
||
|
// Array of globs.
|
||
|
return config.some(c => (0, _minimatch2.default)(filename, c) || (0, _minimatch2.default)(filename, _path2.default.join(process.cwd(), c)));
|
||
|
}
|
||
|
|
||
|
module.exports = {
|
||
|
meta: {
|
||
|
type: 'problem',
|
||
|
docs: {
|
||
|
url: (0, _docsUrl2.default)('no-extraneous-dependencies')
|
||
|
},
|
||
|
|
||
|
schema: [{
|
||
|
'type': 'object',
|
||
|
'properties': {
|
||
|
'devDependencies': { 'type': ['boolean', 'array'] },
|
||
|
'optionalDependencies': { 'type': ['boolean', 'array'] },
|
||
|
'peerDependencies': { 'type': ['boolean', 'array'] },
|
||
|
'bundledDependencies': { 'type': ['boolean', 'array'] },
|
||
|
'packageDir': { 'type': ['string', 'array'] }
|
||
|
},
|
||
|
'additionalProperties': false
|
||
|
}]
|
||
|
},
|
||
|
|
||
|
create: function (context) {
|
||
|
const options = context.options[0] || {};
|
||
|
const filename = context.getFilename();
|
||
|
const deps = getDependencies(context, options.packageDir) || extractDepFields({});
|
||
|
|
||
|
const depsOptions = {
|
||
|
allowDevDeps: testConfig(options.devDependencies, filename) !== false,
|
||
|
allowOptDeps: testConfig(options.optionalDependencies, filename) !== false,
|
||
|
allowPeerDeps: testConfig(options.peerDependencies, filename) !== false,
|
||
|
allowBundledDeps: testConfig(options.bundledDependencies, filename) !== false
|
||
|
|
||
|
// todo: use module visitor from module-utils core
|
||
|
};return {
|
||
|
ImportDeclaration: function (node) {
|
||
|
if (node.source) {
|
||
|
reportIfMissing(context, deps, depsOptions, node, node.source.value);
|
||
|
}
|
||
|
},
|
||
|
ExportNamedDeclaration: function (node) {
|
||
|
if (node.source) {
|
||
|
reportIfMissing(context, deps, depsOptions, node, node.source.value);
|
||
|
}
|
||
|
},
|
||
|
ExportAllDeclaration: function (node) {
|
||
|
if (node.source) {
|
||
|
reportIfMissing(context, deps, depsOptions, node, node.source.value);
|
||
|
}
|
||
|
},
|
||
|
CallExpression: function handleRequires(node) {
|
||
|
if ((0, _staticRequire2.default)(node)) {
|
||
|
reportIfMissing(context, deps, depsOptions, node, node.arguments[0].value);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ydWxlcy9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llcy5qcyJdLCJuYW1lcyI6WyJoYXNLZXlzIiwib2JqIiwiT2JqZWN0Iiwia2V5cyIsImxlbmd0aCIsImFycmF5T3JLZXlzIiwiYXJyYXlPck9iamVjdCIsIkFycmF5IiwiaXNBcnJheSIsImV4dHJhY3REZXBGaWVsZHMiLCJwa2ciLCJkZXBlbmRlbmNpZXMiLCJkZXZEZXBlbmRlbmNpZXMiLCJvcHRpb25hbERlcGVuZGVuY2llcyIsInBlZXJEZXBlbmRlbmNpZXMiLCJidW5kbGVkRGVwZW5kZW5jaWVzIiwiYnVuZGxlRGVwZW5kZW5jaWVzIiwiZ2V0RGVwZW5kZW5jaWVzIiwiY29udGV4dCIsInBhY2thZ2VEaXIiLCJwYXRocyIsInBhY2thZ2VDb250ZW50IiwicGF0aCIsInJlc29sdmUiLCJtYXAiLCJkaXIiLCJmb3JFYWNoIiwiX3BhY2thZ2VDb250ZW50IiwiSlNPTiIsInBhcnNlIiwiZnMiLCJyZWFkRmlsZVN5bmMiLCJqb2luIiwiZGVwc0tleSIsImFzc2lnbiIsInJlYWRQa2dVcCIsInN5bmMiLCJjd2QiLCJnZXRGaWxlbmFtZSIsIm5vcm1hbGl6ZSIsInNvbWUiLCJlIiwiY29kZSIsInJlcG9ydCIsIm1lc3NhZ2UiLCJsb2MiLCJsaW5lIiwiY29sdW1uIiwibmFtZSIsIlN5bnRheEVycm9yIiwibWlzc2luZ0Vycm9yTWVzc2FnZSIsInBhY2thZ2VOYW1lIiwiZGV2RGVwRXJyb3JNZXNzYWdlIiwib3B0RGVwRXJyb3JNZXNzYWdlIiwicmVwb3J0SWZNaXNzaW5nIiwiZGVwcyIsImRlcHNPcHRpb25zIiwibm9kZSIsImltcG9ydEtpbmQiLCJyZXNvbHZlZCIsInNwbGl0TmFtZSIsInNwbGl0Iiwic2xpY2UiLCJpc0luRGVwcyIsInVuZGVmaW5lZCIsImlzSW5EZXZEZXBzIiwiaXNJbk9wdERlcHMiLCJpc0luUGVlckRlcHMiLCJpc0luQnVuZGxlZERlcHMiLCJpbmRleE9mIiwiYWxsb3dEZXZEZXBzIiwiYWxsb3dQZWVyRGVwcyIsImFsbG93T3B0RGVwcyIsImFsbG93QnVuZGxlZERlcHMiLCJ0ZXN0Q29uZmlnIiwiY29uZmlnIiwiZmlsZW5hbWUiLCJjIiwicHJvY2VzcyIsIm1vZHVsZSIsImV4cG9ydHMiLCJtZXRhIiwidHlwZSIsImRvY3MiLCJ1cmwiLCJzY2hlbWEiLCJjcmVhdGUiLCJvcHRpb25zIiwiSW1wb3J0RGVjbGFyYXRpb24iLCJzb3VyY2UiLCJ2YWx1ZSIsIkV4cG9ydE5hbWVkRGVjbGFyYXRpb24iLCJFeHBvcnRBbGxEZWNsYXJhdGlvbiIsIkNhbGxFeHByZXNzaW9uIiwiaGFuZGxlUmVxdWlyZXMiLCJhcmd1bWVudHMiXSwibWFwcGluZ3MiOiI7O0FBQUE7Ozs7QUFDQTs7OztBQUNBOzs7O0FBQ0E7Ozs7QUFDQTs7OztBQUNBOzs7O0FBQ0E7Ozs7QUFDQTs7Ozs7O0FBRUEsU0FBU0EsT0FBVCxHQUEyQjtBQUFBLE1BQVZDLEdBQVUsdUVBQUosRUFBSTs7QUFDekIsU0FBT0MsT0FBT0MsSUFBUCxDQUFZRixHQUFaLEVBQWlCRyxNQUFqQixHQUEwQixDQUFqQztBQUNEOztBQUVELFNBQVNDLFdBQVQsQ0FBcUJDLGFBQXJCLEVBQW9DO0FBQ2xDLFNBQU9DLE1BQU1DLE9BQU4sQ0FBY0YsYUFBZCxJQUErQkEsYUFBL0IsR0FBK0NKLE9BQU9DLElBQVAsQ0FBWUcsYUFBWixDQUF0RDtBQUNEOztBQUVELFNBQVNHLGdCQUFULENBQTBCQyxHQUExQixFQUErQjtBQUM3QixTQUFPO0FBQ0xDLGtCQUFjRCxJQUFJQyxZQUFKLElBQW9CLEVBRDdCO0FBRUxDLHFCQUFpQkYsSUFBSUUsZUFBSixJQUF1QixFQUZuQztBQUdMQywwQkFBc0JILElBQUlHLG9CQUFKLElBQTRCLEVBSDdDO0FBSUxDLHNCQUFrQkosSUFBSUksZ0JBQUosSUFBd0IsRUFKckM7QUFLTDtBQUNBO0FBQ0FDLHlCQUFxQlYsWUFBWUssSUFBSU0sa0JBQUosSUFBMEJOLElBQUlLLG1CQUE5QixJQUFxRCxFQUFqRTtBQVBoQixHQUFQO0FBU0Q7O0FBRUQsU0FBU0UsZUFBVCxDQUF5QkMsT0FBekIsRUFBa0NDLFVBQWxDLEVBQThDO0FBQzVDLE1BQUlDLFFBQVEsRUFBWjtBQUNBLE1BQUk7QUFDRixVQUFNQyxpQkFBaUI7QUFDckJWLG9CQUFjLEVBRE87QUFFckJDLHVCQUFpQixFQUZJO0FBR3JCQyw0QkFBc0IsRUFIRDtBQUlyQkMsd0JBQWtCLEVBSkc7QUFLckJDLDJCQUFxQjtBQUxBLEtBQXZCOztBQVFBLFFBQUlJLGNBQWNBLFdBQVdmLE1BQVgsR0FBb0IsQ0FBdEMsRUFBeUM7QUFDdkMsVUFBSSxDQUFDRyxNQUFNQyxPQUFOLENBQWNXLFVBQWQsQ0FBTCxFQUFnQztBQUM5QkMsZ0JBQVEsQ0FBQ0UsZUFBS0MsT0FBTCxDQUFhSixVQUFiLENBQUQsQ0FBUjtBQUNELE9BRkQsTUFFTztBQUNMQyxnQkFBUUQsV0FBV0ssR0FBWCxDQUFlQyxPQUFPSCxlQUFLQyxPQUFMLENBQWFFLEdBQWIsQ0FBdEIsQ0FBUjtBQUNEO0FBQ0Y7O0FBRUQsUUFBSUwsTUFBTWhCLE1BQU4sR0FBZSxDQUFuQixFQUFzQjtBQUNwQjtBQUNBZ0IsWUFBTU0sT0FBTixDQUFjRCxPQUFPO0FBQ25CLGNBQU1FLGtCQUFrQmxCLGlCQUN0Qm1CLEtBQUtDLEtBQUwsQ0FBV0MsYUFBR0MsWUFBSCxDQUFnQlQsZUFBS1UsSUFBTCxDQUFVUCxHQUFWLEVBQWUsY0FBZixDQUFoQixFQUFnRCxNQUFoRCxDQUFYLENBRHNCLENBQXhCO0FBR0F2QixlQUFPQyxJQUFQLENBQVlrQixjQUFaLEVBQTRCSyxPQUE1QixDQUFvQ08sV0FDbEMvQixPQUFPZ0MsTUFBUCxDQUFjYixlQUFlWSxPQUFmLENBQWQsRUFBdUNOLGdCQUFnQk0sT0FBaEIsQ0FBdkMsQ0FERjtBQUdELE9BUEQ7QUFRRCxLQVZELE1BVU87QUFDTDtBQUNBL0IsYUFBT2dDLE1BQVAsQ0FDRWIsY0FERixFQUVFWixpQkFDRTBCLG9CQUFVQyxJQUFWLENBQWUsRUFBQ0MsS0FBS25CLFFBQVFvQixXQUFSLEVBQU4sRUFBNkJDLFdBQVcsS0FBeEMsRUFBZixFQUErRDdCLEdBRGpFLENBRkY7QUFNRDs7QUFFRCxRQUFJLENBQUMsQ0FDSFcsZUFBZVYsWUFEWixFQUVIVSxlQUFlVCxlQUZaLEVBR0hTLGVBQWVSLG9CQUhaLEVBSUhRLGVBQWVQLGdCQUpaLEVBS0hPLGVBQWVOLG1CQUxaLEVBTUh5QixJQU5HLENBTUV4QyxPQU5GLENBQUwsRUFNaUI7QUFDZixhQUFPLElBQVA7QUFDRDs7QUFFRCxXQUFPcUIsY0FBUDtBQUNELEdBaERELENBZ0RFLE9BQU9vQixDQUFQLEVBQVU7QUFDVixRQUFJckIsTUFBTWhCLE1BQU4sR0FBZSxDQUFmLElBQW9CcUMsRUFBRUMsSUF
|