atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
161 lines (147 loc) • 4.51 kB
JavaScript
'use strict';
/* @noflow */
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
/* NON-TRANSPILED FILE */
/* eslint-disable babel/func-params-comma-dangle, prefer-object-spread/prefer-object-spread */
const resolveFrom = require('resolve-from');
const path = require('path');
const fs = require('fs');
module.exports = function(context) {
const filename = context.getFilename();
const dirname = path.dirname(filename);
const ownPackage = getPackage(filename);
const whitelist =
new Set(context.options[0] && context.options[0].whitelist || []);
if (!ownPackage) {
throw new Error(`"${filename}" does not have a package.json`);
}
// Packages come in 3 flavors:
// packageType: Atom & testRunner: apm
// packageType: Node & testRunner: apm
// packageType: Node & testRunner: npm
function getCrossImportPackage(id) {
const resolved = resolveFrom(dirname, id);
// Exclude modules that are not found or not ours.
if (resolved == null || resolved.includes('/node_modules/')) {
return null;
}
const resolvedPackage = getPackage(resolved);
// Requiring anything within our own package is ok.
if (resolvedPackage.__dirname === ownPackage.__dirname) {
return null;
}
// An apm package requiring a whitelisted package is ok.
if (ownPackage.nuclide &&
ownPackage.nuclide.testRunner === 'apm' &&
whitelist.has(resolvedPackage.name)) {
return null;
}
// Nothing can require into an Atom package
if (resolvedPackage.nuclide &&
resolvedPackage.nuclide.packageType === 'Atom') {
return {type: 'NO_ATOM', pkg: resolvedPackage};
}
// npm packages can only require other npm packages.
if (ownPackage.nuclide &&
ownPackage.nuclide.testRunner === 'npm' &&
resolvedPackage.nuclide &&
resolvedPackage.nuclide.testRunner === 'apm') {
return {type: 'NO_NPM_TO_APM', pkg: resolvedPackage};
}
return null;
}
function reportError(node, action, result) {
const message =
result.type === 'NO_ATOM'
? 'Atom package "{{name}}" is not {{action}} from other packages.' :
result.type === 'NO_NPM_TO_APM'
? 'apm package "{{name}}" is not {{action}} from an npm package.' :
null;
context.report({
node,
data: {name: result.pkg.name, action},
message,
});
}
return {
CallExpression(node) {
if (!isRequire(node)) {
return;
}
// require("…")
const id = node.arguments[0].value;
const result = getCrossImportPackage(id);
if (result) {
reportError(node, 'requireable', result);
}
},
ExportNamedDeclaration(node) {
if (node.source == null || node.exportKind === 'type') {
return;
}
// export foo from "…"
const id = node.source.value;
const result = getCrossImportPackage(id);
if (result) {
reportError(node, 'exportable', result);
}
},
ExportAllDeclaration(node) {
// export * from "…"
const id = node.source.value;
const result = getCrossImportPackage(id);
if (result) {
reportError(node, 'exportable', result);
}
},
ImportDeclaration(node) {
if (node.importKind === 'type' || node.importKind === 'typeof') {
return;
}
// import foo from "…"
const id = node.source.value;
const result = getCrossImportPackage(id);
if (result) {
reportError(node, 'importable', result);
}
},
};
};
function getPackage(start) {
let current = path.resolve(start);
for (;;) {
const filename = path.join(current, 'package.json');
try {
const source = fs.readFileSync(filename, 'utf8');
const json = JSON.parse(source);
json.__filename = filename;
json.__dirname = current;
return json;
} catch (err) {
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
const next = path.join(current, '..');
if (next === current) {
return null;
} else {
current = next;
}
} else {
throw err;
}
}
}
}
function isRequire(node) {
return (
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments[0] &&
node.arguments[0].type === 'Literal'
);
}