|
168 | 168 |
|
169 | 169 | return {defs: defs, |
170 | 170 | passes: {preCondenseReach: preCondenseReach, |
171 | | - postLoadDef: postLoadDef}}; |
| 171 | + postLoadDef: postLoadDef, |
| 172 | + completion: completion}}; |
172 | 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; |
| 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 | + } |
173 | 256 |
|
| 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 | + |
174 | 286 | tern.defineQueryType("node_exports", { |
175 | 287 | takesFile: true, |
176 | 288 | run: function(server, query, file) { |
|
0 commit comments