Skip to content
This repository was archived by the owner on Oct 20, 2023. It is now read-only.

Commit 9253c46

Browse files
angelozerrmarijnh
authored andcommitted
Support completion for node require(' // here Ctrl+Space shows fs, etc
1 parent ec16bc7 commit 9253c46

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

lib/tern.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@
505505
return pos;
506506
}
507507

508-
function resolvePos(file, pos, tolerant) {
508+
var resolvePos = exports.resolvePos = function(file, pos, tolerant) {
509509
if (typeof pos != "number") {
510510
var lineStart = findLineStart(file, pos.line);
511511
if (lineStart == null) {
@@ -539,7 +539,7 @@
539539
return {line: line, ch: pos - lineStart};
540540
}
541541

542-
function outputPos(query, file, pos) {
542+
var outputPos = exports.outputPos = function(query, file, pos) {
543543
if (query.lineCharPositions) {
544544
var out = asLineChar(file, pos);
545545
if (file.type == "part")

plugin/node.js

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,121 @@
168168

169169
return {defs: defs,
170170
passes: {preCondenseReach: preCondenseReach,
171-
postLoadDef: postLoadDef}};
171+
postLoadDef: postLoadDef,
172+
completion: completion}};
172173
});
174+
175+
/**
176+
* Returns completion for require modules.
177+
*/
178+
function completion(file, query) {
179+
var wordPos = tern.resolvePos(file, query.end);
180+
var word = null, completions = [];
181+
var cx = infer.cx(), server = cx.parent, data = server._node, locals = cx.definitions.node;
182+
var currentFile = data.currentFile || resolveProjectPath(server, file.name);
183+
var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins;
184+
185+
function gather(modules) {
186+
// loop for each modules
187+
for(var name in modules) {
188+
if (name != currentFile) {
189+
// current module is different from the current file module
190+
// resolve the module path
191+
var moduleName = resolveModulePath(name, currentFile);
192+
if (moduleName && !(query.filter !== false && word &&
193+
(query.caseInsensitive ? moduleName.toLowerCase() : moduleName).indexOf(word) !== 0)) {
194+
// module path matches the current word, add the module to the completion
195+
var rec = wrapAsObjs ? {name: moduleName} : moduleName;
196+
completions.push(rec);
197+
198+
if ((query.types || query.docs || query.urls || query.origins)) {
199+
var val = modules[name];
200+
infer.resetGuessing();
201+
var type = val.getType();
202+
rec.guess = infer.didGuess();
203+
if (query.types)
204+
rec.type = "module";
205+
if (query.docs)
206+
maybeSet(rec, "doc", val.doc || type && type.doc);
207+
if (query.urls)
208+
maybeSet(rec, "url", val.url || type && type.url);
209+
if (query.origins)
210+
maybeSet(rec, "origin", val.origin || type && type.origin);
211+
}
212+
}
213+
}
214+
}
215+
}
216+
217+
function getQuote(c) {
218+
return c === '\'' || c === '"' ? c : null;
219+
}
220+
221+
var callExpr = infer.findExpressionAround(file.ast, null, wordPos, file.scope, "CallExpression");
222+
if (callExpr && callExpr.node.arguments && callExpr.node.callee && callExpr.node.callee.name === 'require') {
223+
var nodeArg = callExpr.node.arguments[0];
224+
if (nodeArg) {
225+
// here we are inside require(' node string
226+
// compute the word to search and word start
227+
var startQuote = getQuote(nodeArg.raw.charAt(0)),
228+
endQuote = getQuote(nodeArg.raw.length > 1 ? nodeArg.raw.charAt(nodeArg.raw.length - 1) : null);
229+
var wordEnd = endQuote != null ? nodeArg.end - 1: nodeArg.end,
230+
wordStart = startQuote != null ? nodeArg.start + 1: nodeArg.start,
231+
word = nodeArg.value.slice(0, nodeArg.value.length - (wordEnd - wordPos));
232+
if (query.caseInsensitive) word = word.toLowerCase();
233+
// search from local node modules (fs, os, etc)
234+
gather(locals);
235+
// search from custom node modules
236+
gather(data.modules);
237+
return {start: tern.outputPos(query, file, wordStart),
238+
end: tern.outputPos(query, file, wordEnd),
239+
isProperty: false,
240+
isStringAround: true,
241+
startQuote: startQuote,
242+
endQuote: endQuote,
243+
completions: completions};
244+
}
245+
}
246+
}
247+
248+
/**
249+
* Resolve the module path of the given module name by using the current file.
250+
*/
251+
function resolveModulePath(name, currentFile) {
252+
253+
function startsWith(str, prefix) {
254+
return str.slice(0, prefix.length) == prefix;
255+
}
173256

257+
function endsWith(str, suffix) {
258+
return str.slice(-suffix.length) == suffix;
259+
}
260+
261+
if (name.indexOf('/') == -1) return name;
262+
// module name has '/', compute the module path
263+
var modulePath = normPath(relativePath(currentFile + '/..', name));
264+
if (startsWith(modulePath, 'node_modules')) {
265+
// module name starts with node_modules, remove it
266+
modulePath = modulePath.substring('node_modules'.length + 1, modulePath.length);
267+
if (endsWith(modulePath, 'index.js')) {
268+
// module name ends with index.js, remove it.
269+
modulePath = modulePath.substring(0, modulePath.length - 'index.js'.length - 1);
270+
}
271+
} else if (!startsWith(modulePath, '../')) {
272+
// module name is not inside node_modules and there is not ../, add ./
273+
modulePath = './' + modulePath;
274+
}
275+
if (endsWith(modulePath, '.js')) {
276+
// remove js extension
277+
modulePath = modulePath.substring(0, modulePath.length - '.js'.length);
278+
}
279+
return modulePath;
280+
}
281+
282+
function maybeSet(obj, prop, val) {
283+
if (val != null) obj[prop] = val;
284+
}
285+
174286
tern.defineQueryType("node_exports", {
175287
takesFile: true,
176288
run: function(server, query, file) {

test/cases/node/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,8 @@ doc.f1; //doc: doc for f1
6969
doc.f2; //doc: doc for f2
7070

7171
module.exports. //+
72+
73+
// completion on known require module
74+
require("f //+ fs
75+
// completion on custom require module
76+
require("my //+ mymod

0 commit comments

Comments
 (0)