Skip to content

Commit c01a898

Browse files
committed
Add no-missing-import rule
1 parent fe27e32 commit c01a898

11 files changed

+448
-159
lines changed

docs/rules/no-missing-import.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# (no-missing-import)
2+
3+
## Rule Details
4+
5+
The following patterns are considered problems:
6+
7+
```js
8+
```
9+
10+
The following patterns are considered not problems:
11+
12+
```js
13+
```
14+
15+
## When Not To Use It
16+
17+
If you don't want to ***, then it's safe to disable this rule.

lib/check-existence.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @fileoverview Rule to check whether or not `require()` is valid.
3+
* @author Toru Nagashima
4+
* @copyright 2015 Toru Nagashima. All rights reserved.
5+
* See LICENSE file in root directory for full license.
6+
*/
7+
8+
"use strict";
9+
10+
//------------------------------------------------------------------------------
11+
// Requirements
12+
//------------------------------------------------------------------------------
13+
14+
var path = require("path");
15+
var resolve = require("resolve");
16+
var exists = require("./exists");
17+
18+
//------------------------------------------------------------------------------
19+
// Public Interface
20+
//------------------------------------------------------------------------------
21+
22+
/**
23+
* @typedef object TargetInfo
24+
* @property {ASTNode} node - The `Identifier` node of the target.
25+
* @property {string} name - The target name.
26+
* @property {boolean} relative - The flag which shows the target name is a relative path.
27+
*/
28+
29+
/**
30+
* Checks whether or not each requirement target exists.
31+
*
32+
* It looks up the target according to the logic of Node.js.
33+
* See Also: https://nodejs.org/api/modules.html
34+
*
35+
* @param {RuleContext} context - A context to report.
36+
* @param {string} filePath - The current file path.
37+
* @param {TargetInfo[]} targets - A list of target information to check.
38+
* @returns {void}
39+
*/
40+
module.exports = function checkForExistence(context, filePath, targets) {
41+
var basedir = path.dirname(filePath);
42+
var opts = {basedir: basedir};
43+
44+
for (var i = 0; i < targets.length; ++i) {
45+
var target = targets[i];
46+
47+
// Workaround for https://github.com/substack/node-resolve/issues/78
48+
if (target.relative) {
49+
var ext = path.extname(target.name);
50+
var name = ext ? target.name : target.name + ".js";
51+
if (exists(path.resolve(basedir, name))) {
52+
continue;
53+
}
54+
} else {
55+
try {
56+
resolve.sync(target.name, opts);
57+
continue;
58+
} catch (err) {
59+
// ignore.
60+
}
61+
}
62+
63+
context.report({
64+
node: target.node,
65+
message: "\"{{name}}\" is not found.",
66+
data: target
67+
});
68+
}
69+
};

lib/check-publish.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @fileoverview Rule to check whether or not `require()` is valid.
3+
* @author Toru Nagashima
4+
* @copyright 2015 Toru Nagashima. All rights reserved.
5+
* See LICENSE file in root directory for full license.
6+
*/
7+
8+
"use strict";
9+
10+
//------------------------------------------------------------------------------
11+
// Requirements
12+
//------------------------------------------------------------------------------
13+
14+
var path = require("path");
15+
var minimatch = require("minimatch");
16+
var assign = require("object-assign");
17+
var getPackageJson = require("./get-package-json");
18+
19+
//------------------------------------------------------------------------------
20+
// Helpers
21+
//------------------------------------------------------------------------------
22+
23+
/**
24+
* Gets the module name of a given path.
25+
*
26+
* e.g. `eslint/lib/ast-utils` -> `eslint`
27+
*
28+
* @param {string} nameOrPath - A path to get.
29+
* @returns {string} The module name of the path.
30+
*/
31+
function getModuleName(nameOrPath) {
32+
var end = nameOrPath.indexOf("/");
33+
if (end !== -1 && nameOrPath[0] === "@") {
34+
end = nameOrPath.indexOf("/", 1 + end);
35+
}
36+
37+
return end === -1 ? nameOrPath : nameOrPath.slice(0, end);
38+
}
39+
40+
//------------------------------------------------------------------------------
41+
// Public Interface
42+
//------------------------------------------------------------------------------
43+
44+
/**
45+
* @typedef object TargetInfo
46+
* @property {ASTNode} node - The `Identifier` node of the target.
47+
* @property {string} name - The target name.
48+
* @property {boolean} relative - The flag which shows the target name is a relative path.
49+
*/
50+
51+
/**
52+
* Checks whether or not each requirement target is published via package.json.
53+
*
54+
* It reads package.json and checks the target exists in `dependencies`.
55+
*
56+
* @param {RuleContext} context - A context to report.
57+
* @param {string} filePath - The current file path.
58+
* @param {TargetInfo[]} targets - A list of target information to check.
59+
* @returns {void}
60+
*/
61+
module.exports = function checkForPublish(context, filePath, targets) {
62+
var option = context.options[0];
63+
if ((option && option.publish) === null) {
64+
return;
65+
}
66+
67+
var packageInfo = getPackageJson(filePath);
68+
if (!packageInfo) {
69+
return;
70+
}
71+
72+
var publish = minimatch(
73+
path.relative(packageInfo.filePath, filePath).replace(/\\/g, "/"),
74+
(option && option.publish) || "+(./*|./{bin,lib,src}/**)",
75+
{matchBase: true}
76+
);
77+
var deps = assign(
78+
{},
79+
packageInfo.peerDependencies || {},
80+
packageInfo.dependencies || {},
81+
(!publish && packageInfo.devDependencies) || {}
82+
);
83+
84+
for (var i = 0; i < targets.length; ++i) {
85+
var target = targets[i];
86+
87+
if (!target.relative && !deps.hasOwnProperty(getModuleName(target.name))) {
88+
context.report({
89+
node: target.node,
90+
message: "\"{{name}}\" is not published.",
91+
data: target
92+
});
93+
}
94+
}
95+
};

lib/exists.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
var fs = require("fs");
1414

1515
//------------------------------------------------------------------------------
16-
// Rule Definition
16+
// Public Interface
1717
//------------------------------------------------------------------------------
1818

1919
/**

lib/get-package-json.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var exists = require("./exists");
2020
var cache = Object.create(null);
2121

2222
//------------------------------------------------------------------------------
23-
// Rule Definition
23+
// Public Interface
2424
//------------------------------------------------------------------------------
2525

2626
/**

lib/get-value-if-string.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @author Toru Nagashima
3+
* @copyright 2015 Toru Nagashima. All rights reserved.
4+
* See LICENSE file in root directory for full license.
5+
*/
6+
7+
"use strict";
8+
9+
//------------------------------------------------------------------------------
10+
// Public Interface
11+
//------------------------------------------------------------------------------
12+
13+
/**
14+
* Gets the value of a given node if it's a literal or a template literal.
15+
*
16+
* @param {ASTNode} node - A node to get.
17+
* @returns {string|null} The value of the node, or `null`.
18+
*/
19+
module.exports = function getValueIfString(node) {
20+
if (!node) {
21+
return null;
22+
}
23+
24+
switch (node.type) {
25+
case "Literal":
26+
if (typeof node.value === "string") {
27+
return node.value;
28+
}
29+
break;
30+
31+
case "TemplateLiteral":
32+
if (node.expressions.length === 0) {
33+
return node.quasis[0].value.cooked;
34+
}
35+
break;
36+
37+
// no default
38+
}
39+
40+
return null;
41+
};

lib/rules/no-missing-import.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @fileoverview Rule to
3+
* @author Toru Nagashima
4+
* @copyright 2015 Toru Nagashima. All rights reserved.
5+
* See LICENSE file in root directory for full license.
6+
*/
7+
8+
"use strict";
9+
10+
//------------------------------------------------------------------------------
11+
// Requirements
12+
//------------------------------------------------------------------------------
13+
14+
var resolve = require("resolve");
15+
var checkExistence = require("../check-existence");
16+
var checkPublish = require("../check-publish");
17+
var getValueIfString = require("../get-value-if-string");
18+
19+
//------------------------------------------------------------------------------
20+
// Rule Definition
21+
//------------------------------------------------------------------------------
22+
23+
module.exports = function(context) {
24+
/**
25+
* Checks the `source` of a given node is valid.
26+
*
27+
* @param {ASTNode} node - A node to check.
28+
* @returns {void}
29+
*/
30+
function checkForImportExport(node) {
31+
var filePath = context.getFilename();
32+
var name = getValueIfString(node.source);
33+
if (filePath === "<input>" || !name || resolve.isCore(name)) {
34+
return;
35+
}
36+
37+
var targets = [{
38+
node: node.source,
39+
name: name,
40+
relative: /^\./.test(name)
41+
}];
42+
checkExistence(context, filePath, targets);
43+
checkPublish(context, filePath, targets);
44+
}
45+
46+
return {
47+
ImportDeclaration: checkForImportExport,
48+
ExportNamedDeclaration: checkForImportExport,
49+
ExportDefaultDeclaration: checkForImportExport,
50+
ExportAllDeclaration: checkForImportExport
51+
};
52+
};
53+
54+
module.exports.schema = [
55+
{
56+
"type": "object",
57+
"properties": {
58+
"publish": {"type": ["string", "null"]}
59+
},
60+
"additionalProperties": false
61+
}
62+
];

0 commit comments

Comments
 (0)