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

Commit d9f4e76

Browse files
committed
[node plugin] Go over require completion code
Issue angelozerr#430
1 parent 9253c46 commit d9f4e76

File tree

2 files changed

+74
-76
lines changed

2 files changed

+74
-76
lines changed

plugin/node.js

Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -169,95 +169,93 @@
169169
return {defs: defs,
170170
passes: {preCondenseReach: preCondenseReach,
171171
postLoadDef: postLoadDef,
172-
completion: completion}};
172+
completion: findCompletions}};
173173
});
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;
174+
175+
// Completes CommonJS module names in strings passed to require
176+
function findCompletions(file, query) {
177+
var wordEnd = tern.resolvePos(file, query.end);
178+
var callExpr = infer.findExpressionAround(file.ast, null, wordEnd, file.scope, "CallExpression");
179+
if (!callExpr) return;
180+
var callNode = callExpr.node;
181+
if (callNode.callee.type != "Identifier" || callNode.callee.name != "require" ||
182+
callNode.arguments.length < 1) return;
183+
var argNode = callNode.arguments[0];
184+
if (argNode.type != "Literal" || typeof argNode.value != "string" ||
185+
argNode.start > wordEnd || argNode.end < wordEnd) return;
186+
187+
var word = argNode.raw.slice(1, wordEnd - argNode.start), quote = argNode.raw.charAt(0);
188+
if (word && word.charAt(word.length - 1) == quote)
189+
word = word.slice(0, word.length - 1);
190+
var completions = completeModuleName(query, file, word);
191+
if (argNode.end == wordEnd + 1 && file.text.charAt(wordEnd) == quote)
192+
++wordEnd;
193+
return {
194+
start: tern.outputPos(query, file, argNode.start),
195+
end: tern.outputPos(query, file, wordEnd),
196+
isProperty: false,
197+
completions: completions.map(function(name) {
198+
var string = JSON.stringify(name);
199+
if (quote == "'") string = quote + string.slice(1, string.length -1).replace(/'/g, "\\'") + quote;
200+
return string;
201+
})
202+
};
203+
}
204+
205+
function completeModuleName(query, file, word) {
206+
var completions = [];
207+
var cx = infer.cx(), server = cx.parent, data = server._node;
182208
var currentFile = data.currentFile || resolveProjectPath(server, file.name);
183209
var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins;
184-
210+
185211
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 &&
212+
for (var name in modules) {
213+
if (name == currentFile) continue;
214+
215+
var moduleName = resolveModulePath(name, currentFile);
216+
if (moduleName &&
217+
!(query.filter !== false && word &&
193218
(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-
}
219+
var rec = wrapAsObjs ? {name: moduleName} : moduleName;
220+
completions.push(rec);
221+
222+
if (query.types || query.docs || query.urls || query.origins) {
223+
var val = modules[name];
224+
infer.resetGuessing();
225+
var type = val.getType();
226+
rec.guess = infer.didGuess();
227+
if (query.types)
228+
rec.type = infer.toString(val);
229+
if (query.docs)
230+
maybeSet(rec, "doc", val.doc || type && type.doc);
231+
if (query.urls)
232+
maybeSet(rec, "url", val.url || type && type.url);
233+
if (query.origins)
234+
maybeSet(rec, "origin", val.origin || type && type.origin);
212235
}
213236
}
214237
}
215238
}
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-
}
239+
240+
if (query.caseInsensitive) word = word.toLowerCase();
241+
gather(cx.definitions.node);
242+
gather(data.modules);
243+
return completions;
246244
}
247-
245+
248246
/**
249-
* Resolve the module path of the given module name by using the current file.
247+
* Resolve the module path of the given module name by using the current file.
250248
*/
251249
function resolveModulePath(name, currentFile) {
252-
250+
253251
function startsWith(str, prefix) {
254252
return str.slice(0, prefix.length) == prefix;
255253
}
256254

257255
function endsWith(str, suffix) {
258256
return str.slice(-suffix.length) == suffix;
259257
}
260-
258+
261259
if (name.indexOf('/') == -1) return name;
262260
// module name has '/', compute the module path
263261
var modulePath = normPath(relativePath(currentFile + '/..', name));
@@ -269,20 +267,20 @@
269267
modulePath = modulePath.substring(0, modulePath.length - 'index.js'.length - 1);
270268
}
271269
} else if (!startsWith(modulePath, '../')) {
272-
// module name is not inside node_modules and there is not ../, add ./
270+
// module name is not inside node_modules and there is not ../, add ./
273271
modulePath = './' + modulePath;
274272
}
275273
if (endsWith(modulePath, '.js')) {
276274
// remove js extension
277-
modulePath = modulePath.substring(0, modulePath.length - '.js'.length);
278-
}
275+
modulePath = modulePath.substring(0, modulePath.length - '.js'.length);
276+
}
279277
return modulePath;
280278
}
281-
279+
282280
function maybeSet(obj, prop, val) {
283281
if (val != null) obj[prop] = val;
284282
}
285-
283+
286284
tern.defineQueryType("node_exports", {
287285
takesFile: true,
288286
run: function(server, query, file) {

test/cases/node/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,6 @@ doc.f2; //doc: doc for f2
7171
module.exports. //+
7272

7373
// completion on known require module
74-
require("f //+ fs
74+
require('f //+ 'fs'
7575
// completion on custom require module
76-
require("my //+ mymod
76+
require("my //+ "mymod"

0 commit comments

Comments
 (0)