|
169 | 169 | return {defs: defs, |
170 | 170 | passes: {preCondenseReach: preCondenseReach, |
171 | 171 | postLoadDef: postLoadDef, |
172 | | - completion: completion}}; |
| 172 | + completion: findCompletions}}; |
173 | 173 | }); |
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; |
182 | 208 | var currentFile = data.currentFile || resolveProjectPath(server, file.name); |
183 | 209 | var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins; |
184 | | - |
| 210 | + |
185 | 211 | 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 && |
193 | 218 | (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); |
212 | 235 | } |
213 | 236 | } |
214 | 237 | } |
215 | 238 | } |
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; |
246 | 244 | } |
247 | | - |
| 245 | + |
248 | 246 | /** |
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. |
250 | 248 | */ |
251 | 249 | function resolveModulePath(name, currentFile) { |
252 | | - |
| 250 | + |
253 | 251 | function startsWith(str, prefix) { |
254 | 252 | return str.slice(0, prefix.length) == prefix; |
255 | 253 | } |
256 | 254 |
|
257 | 255 | function endsWith(str, suffix) { |
258 | 256 | return str.slice(-suffix.length) == suffix; |
259 | 257 | } |
260 | | - |
| 258 | + |
261 | 259 | if (name.indexOf('/') == -1) return name; |
262 | 260 | // module name has '/', compute the module path |
263 | 261 | var modulePath = normPath(relativePath(currentFile + '/..', name)); |
|
269 | 267 | modulePath = modulePath.substring(0, modulePath.length - 'index.js'.length - 1); |
270 | 268 | } |
271 | 269 | } 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 ./ |
273 | 271 | modulePath = './' + modulePath; |
274 | 272 | } |
275 | 273 | if (endsWith(modulePath, '.js')) { |
276 | 274 | // remove js extension |
277 | | - modulePath = modulePath.substring(0, modulePath.length - '.js'.length); |
278 | | - } |
| 275 | + modulePath = modulePath.substring(0, modulePath.length - '.js'.length); |
| 276 | + } |
279 | 277 | return modulePath; |
280 | 278 | } |
281 | | - |
| 279 | + |
282 | 280 | function maybeSet(obj, prop, val) { |
283 | 281 | if (val != null) obj[prop] = val; |
284 | 282 | } |
285 | | - |
| 283 | + |
286 | 284 | tern.defineQueryType("node_exports", { |
287 | 285 | takesFile: true, |
288 | 286 | run: function(server, query, file) { |
|
0 commit comments