text: /*******************************************************************************
   * @license
   * Copyright (c) 2012 VMware, Inc. All Rights Reserved.
   * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
   * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
   * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
   * You can obtain a current copy of the Eclipse Public License from
   * http://www.opensource.org/licenses/eclipse-1.0.php
   *
   * Contributors:
   *     Andy Clement (VMware) - initial API and implementation
   *     Andrew Eisenberg (VMware) - implemented visitor pattern
   ******************************************************************************/
  
  /*global define require eclipse esprima window */
  define("plugins/esprima/esprimaJsContentAssist", ["plugins/esprima/esprimaVisitor", "plugins/esprima/types", "plugins/esprima/proposalUtils"],
  		function(mVisitor, mTypes, proposalUtils, scriptedLogger) {
  
  	/** @type {function(obj):Boolean} a safe way of checking for arrays */
  	var isArray = Array.isArray;
  	if (!isArray) {
  	    isArray = function isArray(ary) {
  	        return Object.prototype.toString.call(ary) === '[object Array]';
  	    };
  	}
  
  	var RESERVED_WORDS = {
  		"break" : true, "case" : true, "catch" : true, "continue" : true, "debugger" : true, "default" : true, "delete" : true, "do" : true, "else" : true, "finally" : true,
  		"for" : true, "function" : true, "if" : true, "in" : true, "instanceof" : true, "new" : true, "return" : true, "switch" : true, "this" : true, "throw" : true, "try" : true, "typeof" : true,
  		"var" : true, "void" : true, "while" : true, "with" : true
  	};
  	function isReserverdWord(name) {
  		return RESERVED_WORDS[name] === true;
  	}
  
  	/**
  	 * @param {String} char a string of at least one char14acter
  	 * @return {boolean} true iff uppercase ascii character
  	 */
  	function isUpperCaseChar(c) {
  		if (c.length < 1) {
  			return false;
  		}
  		var charCode = c.charCodeAt(0);
  		if (isNaN(charCode)) {
  			return false;
  		}
  		return charCode >= 65 && charCode <= 90;
  	}
  
  	/**
  	 * finds the right-most segment of a dotted MemberExpression
  	 * if it is an identifier, or null otherwise
  	 * @return {{name:String}}
  	 */
  	function findRightMost(node) {
  		if (!node) {
  			return null;
  		} else if (node.type === "Identifier") {
  			return node;
  		} else if (node.type === "MemberExpression") {
  			if (node.computed) {
  				if (node.property.type === "Literal" && typeof node.property.value === "string") {
  					return node.property;
  				} else {
  					// an array access
  					return node;
  				}
  			} else {
  				return findRightMost(node.property);
  			}
  		} else if (node.type === "ArrayExpression") {
  			return node;
  		} else {
  			return null;
  		}
  	}
  
  	/**
  	 * Recursively generates a name based on the given expression
  	 * @param {{type:String,name:String}} node
  	 * @return {String}
  	 */
  	function findDottedName(node) {
  		if (!node) {
  			return "";
  		} else if (node.type === "Identifier") {
  			return node.name;
  		} else if (node.type === "MemberExpression") {
  			var left = findDottedName(node.object);
  			var right = findDottedName(node.property);
  			if (left.length > 0 && right.length > 0) {
  				return left + "." + right;
  			}
  			return left + right;
  		} else if (node.type === "CallExpression") {
  			return findDottedName(node.callee);
  		} else {
  			return "";
  		}
  	}
  
  	/**
  	 * Convert an array of parameters into a string and also compute linked editing positions
  	 * @param {String} name name of the function
  	 * @param {String} type the type of the function using the following structure '?Type:arg1,arg2,...'
  	 * @param {Number} offset offset
  	 * @return {{ completion:String, positions:Array.<Number> }}
  	 */
  	function calculateFunctionProposal(name, type, offset) {
  		var paramsOffset = mTypes.findReturnTypeEnd(type), paramsStr, params;
  		paramsStr = paramsOffset > 0 ? type.substring(paramsOffset+1) : "";
  		params = paramsStr.split(",");
  		if (!paramsStr || params.length === 0) {
  			return {completion: name + "()", positions:null};
  		}
  		var positions = [];
  		var completion = name + '(';
  		var plen = params.length;
  		for (var p = 0; p < plen; p++) {
  			if (p > 0) {
  				completion += ', ';
  			}
  			var argName;
  			if (typeof params[p] === "string") {
  				// need this because jslintworker.js augments the String prototype with a name() function
  				// don't want confusion
  				argName = params[p];
  				var slashIndex = argName.indexOf('/');
  				if (slashIndex > 0) {
  					argName = argName.substring(0, slashIndex);
  				}
  			} else if (params[p].name) {
  				argName = params[p].name();
  			} else {
  				argName = params[p];
  			}
  			positions.push({offset:offset+completion.length+1, length: argName.length});
  			completion += argName;
  		}
  		completion += ')';
  		return {completion: completion, positions: positions.length === 0 ? null : positions};
  	}
  
  	/**
  	 * checks that offset overlaps with the given range
  	 * Since esprima ranges are zero-based, inclusive of
  	 * the first char and exclusive of the last char, must
  	 * use a +1 at the end.
  	 * eg- (^ is the line start)
  	 *       ^x    ---> range[0,0]
  	 *       ^  xx ---> range[2,3]
  	 */
  	function inRange(offset, range, includeEdge) {
  		return range[0] <= offset && (includeEdge ? range[1] >= offset : range[1] > offset);
  	}
  	/**
  	 * checks that offset is before the range
  	 * @return Boolean
  	 */
  	function isBefore(offset, range) {
  		if (!range) {
  			return true;
  		}
  		return offset < range[0];
  	}
  
  	/**
  	 * Determines if the offset is inside this member expression, but after the '.' and before the
  	 * start of the property.
  	 * eg, the following returns true:
  	 *   foo   .^bar
  	 *   foo   .  ^ bar
  	 * The following returns false:
  	 *   foo   ^.  bar
  	 *   foo   .  b^ar
  	 * @return Boolean
  	 */
  	function afterDot(offset, memberExpr, contents) {
  		// check for broken AST
  		var end;
  		if (memberExpr.property) {
  			end = memberExpr.property.range[0];
  		} else {
  			// no property expression, use the end of the memberExpr as the end to look at
  			// in this case assume that the member expression ends just after the dot
  			// this allows content assist invocations to work on the member expression when there
  			// is no property
  			end = memberExpr.range[1] + 2;
  		}
  		// we are not considered "after" the dot if the offset
  		// overlaps with the property expression or if the offset is
  		// after the end of the member expression
  		if (!inRange(offset-1, memberExpr.range) ||
  			inRange(offset-1, memberExpr.object.range) ||
  			offset > end) {
  			return false;
  		}
  
  		var dotLoc = memberExpr.object.range[1];
  		while (contents.charAt(dotLoc) !== "." && dotLoc < end) {
  			dotLoc++;
  		}
  
  		if (contents.charAt(dotLoc) !== ".") {
  			return false;
  		}
  
  		return dotLoc < offset;
  	}
  
  	/**
  	 * @return "top" if we are at a start of a new expression fragment (eg- at an empty line,
  	 * or a new parameter).  "member" if we are after a dot in a member expression.  false otherwise
  	 * @return {Boolean|String}
  	 */
  	function shouldVisit(root, offset, prefix, contents) {
  		/**
  		 * A visitor that finds the parent stack at the given location
  		 * @param node the AST node being visited
  		 * @param parents stack of parent nodes for the current node
  		 * @param isInitialVisit true iff this is the first visit of the node, false if this is
  		 *   the end visit of the node
  		 */
  		var findParent = function(node, parents, isInitialVisit) {
  			// extras prop is where we stuff everything that we have added
  			if (!node.extras) {
  				node.extras = {};
  			}
  
  			if (!isInitialVisit) {
  
  				// if we have reached the end of an inRange block expression then
  				// this means we are completing on an empty expression
  				if (node.type === "Program" || (node.type === "BlockStatement") &&
  						inRange(offset, node.range)) {
  					throw "done";
  				}
  
  				parents.pop();
  				// return value is ignored
  				return false;
  			}
  
  			// the program node is always in range even if the range numbers do not line up
  			if ((node.range && inRange(offset-1, node.range)) || node.type === "Program") {
  				if (node.type === "Identifier") {
  					throw "done";
  				}
  				parents.push(node);
  				if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression") &&
  						node.nody && isBefore(offset, node.body.range)) {
  					// completion occurs on the word "function"
  					throw "done";
  				}
  				// special case where we are completing immediately after a '.'
  				if (node.type === "MemberExpression" && !node.property && afterDot(offset, node, contents)) {
  					throw "done";
  				}
  				return true;
  			} else {
  				return false;
  			}
  		};
  		var parents = [];
  		try {
  			mVisitor.visit(root, parents, findParent, findParent);
  		} catch (done) {
  			if (done !== "done") {
  				// a real error
  				throw(done);
  			}
  		}
  
  		// determine if we need to defer infering the enclosing function block
  		var toDefer;
  		if (parents && parents.length) {
  			var parent = parents.pop();
  			for (var i = 0; i < parents.length; i++) {
  				if ((parents[i].type === "FunctionDeclaration" || parents[i].type === "FunctionExpression") &&
  						// don't defer if offset is over the function name
  						!(parents[i].id && inRange(offset, parents[i].id.range, true))) {
  					toDefer = parents[i];
  					break;
  				}
  
  			}
  
  			if (parent.type === "MemberExpression") {
  				if (parent.property && inRange(offset-1, parent.property.range)) {
  					// on the right hand side of a property, eg: foo.b^
  					return { kind : "member", toDefer : toDefer };
  				} else if (inRange(offset-1, parent.range) && afterDot(offset, parent, contents)) {
  					// on the right hand side of a dot with no text after, eg: foo.^
  					return { kind : "member", toDefer : toDefer };
  				}
  			} else if (parent.type === "Program" || parent.type === "BlockStatement") {
  				// completion at a new expression
  				if (!prefix) {
  				}
  			} else if (parent.type === "VariableDeclarator" && (!parent.init || isBefore(offset, parent.init.range))) {
  				// the name of a variable declaration
  				return false;
  			} else if ((parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression") &&
  					isBefore(offset, parent.body.range)) {
  				// a function declaration
  				return false;
  			}
  		}
  		return { kind : "top", toDefer : toDefer };
  	}
  
  	/**
  	 * finds the final return statement of a function declaration
  	 * @param node an ast statement node
  	 * @return the lexically last ReturnStatment AST node if there is one, else
  	 * null if there is no return statement
  	 */
  	function findReturn(node) {
  		if (!node) {
  			return null;
  		}
  		var type = node.type, maybe, i, last;
  		// since we are finding the last return statement, start from the end
  		switch(type) {
  		case "BlockStatement":
  			if (node.body && node.body.length > 0) {
  				last = node.body[node.body.length-1];
  				if (last.type === "ReturnStatement") {
  					return last;
  				} else {
  					return findReturn(last);
  				}
  			}
  			return null;
  		case "WhileStatement":
  		case "DoWhileStatement":
  		case "ForStatement":
  		case "ForInStatement":
  		case "CatchClause":
  
  			return findReturn(node.body);
  		case "IfStatement":
  			maybe = findReturn(node.alternate);
  			if (!maybe) {
  				maybe = findReturn(node.consequent);
  			}
  			return maybe;
  		case "TryStatement":
  			maybe = findReturn(node.finalizer);
  			var handlers = node.handlers;
  			if (!maybe && handlers) {
  				// start from the last handler
  				for (i = handlers.length-1; i >= 0; i--) {
  					maybe = findReturn(handlers[i]);
  					if (maybe) {
  						break;
  					}
  				}
  			}
  			if (!maybe) {
  				maybe = findReturn(node.block);
  			}
  			return maybe;
  		case "SwitchStatement":
  			var cases = node.cases;
  			if (cases) {
  				// start from the last handler
  				for (i = cases.length-1; i >= 0; i--) {
  					maybe = findReturn(cases[i]);
  					if (maybe) {
  						break;
  					}
  				}
  			}
  			return maybe;
  		case "SwitchCase":
  			if (node.consequent && node.consequent.length > 0) {
  				last = node.consequent[node.consequent.length-1];
  				if (last.type === "ReturnStatement") {
  					return last;
  				} else {
  					return findReturn(last);
  				}
  			}
  			return null;
  
  		case "ReturnStatement":
  			return node;
  		default:
  			// don't visit nested functions
  			// expression statements, variable declarations,
  			// or any other kind of node
  			return null;
  		}
  	}
  
  	/**
  	 * updates a function type to include a new return type.
  	 * function types are specified like this: ?returnType:[arg-n...]
  	 * return type is the name of the return type, arg-n is the name of
  	 * the nth argument.
  	 */
  	function updateReturnType(originalFunctionType, newReturnType) {
  		if (! originalFunctionType) {
  			// not a valid function type
  			return newReturnType;
  		}
  
  		var firstChar = originalFunctionType.charAt(0);
  		if (firstChar !== "?" && firstChar !== "*") {
  			// not a valid function type
  			return newReturnType;
  		}
  
  		var end = mTypes.findReturnTypeEnd(originalFunctionType);
  		if (end < 0) {
  			// not a valid function type
  			return newReturnType;
  		}
  		return firstChar + newReturnType + originalFunctionType.substring(end);
  	}
  	/**
  	 * checks to see if this file looks like an AMD module
  	 * Assumes that there are one or more calls to define at the top level
  	 * and the first statement is a define call
  	 * @return true iff there is a top-level call to 'define'
  	 */
  	function checkForAMD(node) {
  		var body = node.body;
  		if (body && body.length >= 1 && body[0]) {
  			if (body[0].type === "ExpressionStatement" &&
  				body[0].expression &&
  				body[0].expression.type === "CallExpression" &&
  				body[0].expression.callee.name === "define") {
  
  				// found it.
  				return body[0].expression;
  			}
  		}
  		return null;
  	}
  	/**
  	 * checks to see if this file looks like a wrapped commonjs module
  	 * Assumes that there are one or more calls to define at the top level
  	 * and the first statement is a define call
  	 * @return true iff there is a top-level call to 'define'
  	 */
  	function checkForCommonjs(node) {
  		var body = node.body;
  		if (body && body.length >= 1) {
  			for (var i = 0; i < body.length; i++) {
  				if (body[i] &&
  					body[i].type === "ExpressionStatement" &&
  					body[i].expression &&
  					body[i].expression.type === "CallExpression" &&
  					body[i].expression.callee.name === "define") {
  
  					var callee = body[i].expression;
  					if (callee["arguments"] &&
  						callee["arguments"].length === 1 &&
  						callee["arguments"][0].type === "FunctionExpression" &&
  						callee["arguments"][0].params.length === 3) {
  
  						var params = callee["arguments"][0].params;
  						if (params[0].name === "require" &&
  							params[1].name === "exports" &&
  							params[2].name === "module") {
  
  							// found it.
  							return body[i].expression;
  						}
  					}
  				}
  			}
  		}
  		return null;
  	}
  
  	/**
  	 * if this method call ast node is a call to require with a single string constant
  	 * argument, then look that constant up in the indexer to get a summary
  	 * if a summary is found, then apply it to the current scope
  	 */
  	function extractRequireModule(call, env) {
  		if (!env.indexer) {
  			return;
  		}
  		if (call.type === "CallExpression" && call.callee.type === "Identifier" &&
  			call.callee.name === "require" && call["arguments"].length === 1) {
  
  			var arg = call["arguments"][0];
  			if (arg.type === "Literal" && typeof arg.value === "string") {
  				// we're in business
  				var summary = env.indexer.retrieveSummary(arg.value);
  				if (summary) {
  					var typeName;
  					var mergeTypeName;
  					if (typeof summary.provided === "string") {
  						mergeTypeName = typeName = summary.provided;
  					} else {
  						// module provides a composite type
  						// must create a type to add the summary to
  						mergeTypeName = typeName = env.newScope();
  						env.popScope();
  					}
  					env.mergeSummary(summary, mergeTypeName);
  					return typeName;
  				}
  			}
  		}
  
  		return;
  	}
  
  	/**
  	 * checks to see if this function is a module definition
  	 * and if so returns an array of module definitions
  	 *
  	 * if this is not a module definition, then just return an array of Object for each typ
  	 */
  	function findModuleDefinitions(fnode, env) {
  		var paramTypes = [], params = fnode.params, i;
  		if (params.length > 0) {
  			if (!fnode.extras) {
  				fnode.extras = {};
  			}
  			if (env.indexer && fnode.extras.amdDefn) {
  				var args = fnode.extras.amdDefn["arguments"];
  				// the function definition must be the last argument of the call to define or require
  				if (args.length > 1 && args[args.length-1] === fnode) {
  					// the module names could be the first or second argument
  					var moduleNames = null;
  					if (args.length === 3 && args[0].type === "Literal" && args[1].type === "ArrayExpression") {
  						moduleNames = args[1].elements;
  					} else if (args.length === 2 && args[0].type === "ArrayExpression") {
  						moduleNames = args[0].elements;
  					}
  					if (moduleNames) {
  						for (i = 0; i < params.length; i++) {
  							if (i < moduleNames.length && moduleNames[i].type === "Literal") {
  								// resolve the module name from the indexer
  								var summary = env.indexer.retrieveSummary(moduleNames[i].value);
  								if (summary) {
  									var typeName;
  									var mergeTypeName;
  									if (typeof summary.provided === "string") {
  										mergeTypeName = typeName = summary.provided;
  									} else {
  										// module provides a composite type
  										// must create a type to add the summary to
  										mergeTypeName = typeName = env.newScope();
  										env.popScope();
  									}
  									env.mergeSummary(summary, mergeTypeName);
  									paramTypes.push(typeName);
  								} else {
  									paramTypes.push(env.newFleetingObject());
  								}
  							} else {
  								paramTypes.push("Object");
  							}
  						}
  					}
  				}
  			}
  		}
  		if (paramTypes.length === 0) {
  			for (i = 0; i < params.length; i++) {
  				paramTypes.push(env.newFleetingObject());
  			}
  		}
  		return paramTypes;
  	}
  
  
  	/**
  	 * Finds the closest doc comment to this node
  	 * @param {{range:Array.<Number>}} node
  	 * @param {Array.<{range:Array.<Number>}>} doccomments
  	 * @return {{value:String,range:Array.<Number>}}
  	 */
  	function findAssociatedCommentBlock(node, doccomments) {
  		// look for closest doc comment that is before the start of this node
  		// just shift all the other ones
  		var candidate;
  		while (doccomments.length > 0 && doccomments[0].range[0] < node.range[0]) {
  			candidate = doccomments.shift();
  		}
  		return candidate || { value : null };
  	}
  
  
  	/**
  	 * Extracts all doccomments that fall inside the given range.
  	 * Side effect is to remove the array elements
  	 * @param Array.<{range:Array.<Number}>> doccomments
  	 * @param Array.<Number> range
  	 * @return {{value:String,range:Array.<Number>}} array elements that are removed
  	 */
  	function extractDocComments(doccomments, range) {
  		var start = 0, end = 0, i, docStart, docEnd;
  		for (i = 0; i < doccomments.length; i++) {
  			docStart = doccomments[i].range[0];
  			docEnd = doccomments[i].range[1];
  			if (!isBefore(docStart, range) || !isBefore(docEnd, range)) {
  				break;
  			}
  		}
  
  		if (i < doccomments.length) {
  			start = i;
  			for (i = i; i < doccomments.length; i++) {
  				docStart = doccomments[i].range[0];
  				docEnd = doccomments[i].range[1];
  				if (!inRange(docStart, range, true) || !inRange(docEnd, range, true)) {
  					break;
  				}
  			}
  			end = i;
  		}
  
  		return doccomments.splice(start, end-start);
  	}
  
  	/**
  	 * This function takes the current AST node and does the first inferencing step for it.
  	 * @param node the AST node to visit
  	 * @param env the context for the visitor.  See computeProposals below for full description of contents
  	 */
  	function inferencer(node, env) {
  		var type = node.type, name, i, property, params, newTypeName, jsdocResult, jsdocType;
  
  		// extras prop is where we stuff everything that we have added
  		if (!node.extras) {
  			node.extras = {};
  		}
  
  		switch(type) {
  		case "Program":
  			// check for module kind
  			env.commonjsModule = checkForCommonjs(node);
  			if (!env.commonjsModule) {
  				// can't be both amd and commonjs
  				env.amdModule = checkForAMD(node);
  			}
  			break;
  		case "BlockStatement":
  			node.extras.inferredType = env.newScope();
  			if (node.extras.stop) {
  				// this BlockStatement inferencing is deferred until after the rest of the file is inferred
  				inferencerPostOp(node, env);
  				delete node.extras.stop;
  				return false;
  			}
  			break;
  		case "Literal":
  			break;
  		case "ArrayExpression":
  			node.extras.inferredType = "Array";
  			break;
  		case "ObjectExpression":
  			if (node.extras.fname) {
  				// this object expression is contained inside another object expression
  				env.pushName(node.extras.fname);
  			}
  
  			// for object literals, create a new object type so that we can stuff new properties into it.
  			// we might be able to do better by walking into the object and inferring each RHS of a
  			// key-value pair
  			newTypeName = env.newObject(null, node.range);
  			node.extras.inferredType = newTypeName;
  			for (i = 0; i < node.properties.length; i++) {
  				property = node.properties[i];
  				// only remember if the property is an identifier
  				if (property.key && property.key.name) {
  					// first just add as an object property (or use jsdoc if exists).
  					// after finishing the ObjectExpression, go and update
  					// all of the variables to reflect their final inferred type
  					var docComment = findAssociatedCommentBlock(property.key, env.comments);
  					jsdocResult = mTypes.parseJSDocComment(docComment);
  					jsdocType = mTypes.convertJsDocType(jsdocResult.type, env);
  					var keyType = jsdocType ? jsdocType : "Object";
  					env.addVariable(property.key.name, node, keyType, property.key.range, docComment.range);
  					if (!property.key.extras) {
  						property.key.extras = {};
  					}
  					property.key.extras.associatedComment = docComment;
  					// remember that this is the LHS so that we don't add the identifier to global scope
  					property.key.extras.isLHS = property.key.extras.isDecl = true;
  
  					if (property.value.type === "FunctionExpression" || property.value.type === "ObjectExpression") {
  						if (!property.value.extras) {
  							property.value.extras = {};
  						}
  						// RHS is a function, remember the name in case it is a constructor
  						property.value.extras.fname = property.key.name;
  						property.value.extras.cname = env.getQualifiedName() + property.key.name;
  
  						if (property.value.type === "FunctionExpression") {
  							// now remember the jsdocResult so it doesn't need to be recomputed
  							property.value.extras.jsdocResult = jsdocResult;
  						}
  					}
  				}
  			}
  			break;
  		case "FunctionDeclaration":
  		case "FunctionExpression":
  			var nameRange;
  			if (node.id) {
  				// true for function declarations
  				name = node.id.name;
  				nameRange = node.id.range;
  			} else if (node.extras.fname) {
  				// true for rhs of assignment to function expression
  				name = node.extras.fname;
  				nameRange = node.range;
  			}
  			params = [];
  			if (node.params) {
  				for (i = 0; i < node.params.length; i++) {
  					params[i] = node.params[i].name;
  				}
  			}
  
  			if (node.extras.jsdocResult) {
  				jsdocResult = node.extras.jsdocResult;
  				docComment = { range : null };
  			} else {
  				docComment = node.extras.associatedComment || findAssociatedCommentBlock(node, env.comments);
  				jsdocResult = mTypes.parseJSDocComment(docComment);
  			}
  
  			// assume that function name that starts with capital is
  			// a constructor
  			var isConstuctor;
  			if (name && node.body && isUpperCaseChar(name)) {
  				if (node.extras.cname) {
  					// RHS of assignment
  					name = node.extras.cname;
  				}
  				// create new object so that there is a custom "this"
  				newTypeName = env.newObject(name, node.range);
  				isConstuctor = true;
  			} else {
  				var jsdocReturn = mTypes.convertJsDocType(jsdocResult.rturn, env);
  				if (jsdocReturn) {
  					// keep track of the return type for the way out
  					node.extras.jsdocReturn = jsdocReturn;
  					newTypeName = jsdocReturn;
  					node.extras.inferredType = jsdocReturn;
  				} else {
  					// temporarily use "undefined" as type, but this may change once we
  					// walk through to get to a return statement
  					newTypeName = "undefined";
  				}
  				isConstuctor = false;
  			}
  			if (!node.body.extras) {
  				node.body.extras = {};
  			}
  			node.body.extras.isConstructor = isConstuctor;
  
  			// add parameters to the current scope
  			var paramTypeSigs = [], paramSigs = [];
  			if (params.length > 0) {
  				var moduleDefs = findModuleDefinitions(node, env);
  				for (i = 0; i < params.length; i++) {
  					// choose jsdoc tags over module definitions
  					var jsDocParam = jsdocResult.params[params[i]];
  					var typeName = null;
  					if (jsDocParam) {
  						typeName = mTypes.convertJsDocType(jsDocParam, env);
  					}
  					if (!typeName) {
  						typeName = moduleDefs[i];
  					}
  					paramTypeSigs.push(typeName);
  					paramSigs.push(params[i] + "/" + typeName);
  				}
  			}
  
  
  			var functionTypeName = (isConstuctor ? "*" : "?") + newTypeName + ":" + paramSigs.join(",");
  			if (isConstuctor) {
  				env.createConstructor(functionTypeName, newTypeName);
  				// assume that constructor will be available from global scope using qualified name
  				// this is not correct in all cases
  				env.addOrSetGlobalVariable(name, functionTypeName, nameRange, docComment.range);
  			}
  
  			node.extras.inferredType = functionTypeName;
  
  			if (name && !isBefore(env.offset, node.range)) {
  				// if we have a name, then add it to the scope
  				env.addVariable(name, node.extras.target, functionTypeName, nameRange, docComment.range);
  			}
  
  			// now add the scope for inside the function
  			env.newScope();
  			env.addVariable("arguments", node.extras.target, "Arguments", node.range);
  
  
  			// now determine if we need to add 'this'.  If this function has an appliesTo, the we know it is being assigned as a property onto something else
  			// the 'something else' is the 'this' type.
  			// eg- var obj={};var obj.fun=function() { ... };
  			var appliesTo = node.extras.appliesTo;
  			if (appliesTo) {
  				var appliesToOwner = appliesTo.extras.target;
  				if (appliesToOwner) {
  					var ownerTypeName = env.scope(appliesToOwner);
  					// for the special case of adding to the prototype, we want to make sure that we also add to the 'this' of
  					// the instantiated types
  					if (mTypes.isPrototype(ownerTypeName)) {
  						ownerTypeName = mTypes.extractReturnType(ownerTypeName);
  					}
  					env.addVariable("this", node.extras.target, ownerTypeName, nameRange, docComment.range);
  				}
  			}
  
  			// add variables for all parameters
  			for (i = 0; i < params.length; i++) {
  				env.addVariable(params[i], node.extras.target, paramTypeSigs[i], node.params[i].range);
  			}
  			break;
  		case "VariableDeclarator":
  			if (node.id.name) {
  				// remember that the identifier is an LHS
  				// so, don't create a type for it
  				if (!node.id.extras) {
  					node.id.extras = {};
  				}
  				node.id.extras.isLHS = node.id.extras.isDecl = true;
  				if (node.init && !node.init.extras) {
  					node.init.extras = {};
  				}
  				if (node.init && node.init.type === "FunctionExpression") {
  					// RHS is a function, remember the name in case it is a constructor
  					node.init.extras.fname = node.id.name;
  					node.init.extras.cname = env.getQualifiedName() + node.id.name;
  					node.init.extras.fnameRange = node.id.range;
  				} else {
  					// not the RHS of a function, check for jsdoc comments
  					var docComment = findAssociatedCommentBlock(node, env.comments);
  					jsdocResult = mTypes.parseJSDocComment(docComment);
  					jsdocType = mTypes.convertJsDocType(jsdocResult.type, env);
  					node.extras.docRange = docComment.range;
  					if (jsdocType) {
  						node.extras.inferredType = jsdocType;
  						node.extras.jsdocType = jsdocType;
  						env.addVariable(node.id.name, node.extras.target, jsdocType, node.id.range, docComment.range);
  					}
  				}
  			}
  			env.pushName(node.id.name);
  			break;
  		case "AssignmentExpression":
  			var rightMost = findRightMost(node.left);
  			var qualName = env.getQualifiedName() + findDottedName(node.left);
  			if (rightMost && (rightMost.type === "Identifier" || rightMost.type === "Literal")) {
  				if (!rightMost.extras) {
  					rightMost.extras = {};
  				}
  				if (node.right.type === "FunctionExpression") {
  					// RHS is a function, remember the name in case it is a constructor
  					if (!node.right.extras) {
  						node.right.extras = {};
  					}
  					node.right.extras.appliesTo = rightMost;
  					node.right.extras.fname = rightMost.name;
  					node.right.extras.cname = qualName;
  					node.right.extras.fnameRange = rightMost.range;
  
  					if (!node.left.extras) {
  						node.left.extras = {};
  					}
  				}
  				var docComment = findAssociatedCommentBlock(node, env.comments);
  				jsdocResult = mTypes.parseJSDocComment(docComment);
  				jsdocType = mTypes.convertJsDocType(jsdocResult.type, env);
  				node.extras.docRange = docComment.range;
  				if (jsdocType) {
  					node.extras.inferredType = jsdocType;
  					node.extras.jsdocType = jsdocType;
  					env.addVariable(rightMost.name, node.extras.target, jsdocType, rightMost.range, docComment.range);
  				}
  			}
  			env.pushName(qualName);
  			break;
  		case "CatchClause":
  			// create a new scope for the catch parameter
  			node.extras.inferredType = env.newScope();
  			if (node.param) {
  				if (!node.param.extras) {
  					node.param.extras = {};
  				}
  				node.param.extras.inferredType = "Error";
  				env.addVariable(node.param.name, node.extras.target, "Error", node.param.range);
  			}
  			break;
  		case "MemberExpression":
  			if (node.property) {
  				if (!node.computed ||  // like this: foo.at
  					(node.computed && node.property.type === "Literal" && typeof node.property.value === "string")) {  // like this: foo['at']
  
  					// keep track of the target of the property expression
  					// so that its type can be used as the seed for finding properties
  					if (!node.property.extras) {
  						node.property.extras = {};
  					}
  					node.property.extras.target = node.object;
  				} else { // like this: foo[at] or foo[0]
  					// do nothing
  				}
  			}
  			break;
  		case "CallExpression":
  			if (node.callee.name === "define" || node.callee.name === "require") {
  				// check for AMD definition
  				var args = node["arguments"];
  				if (args.length > 1 &&
  					args[args.length-1].type === "FunctionExpression" &&
  					args[args.length-2].type === "ArrayExpression") {
  
  					// assume definition
  					if (!args[args.length-1].extras) {
  						args[args.length-1].extras = {};
  					}
  					args[args.length-1].extras.amdDefn = node;
  				}
  			}
  			break;
  		}
  
  		// defer the inferencing of the function's children containing the offset.
  		if (node === env.defer) {
  			node.extras.associatedComment = findAssociatedCommentBlock(node, env.comments);
  			node.extras.inferredType = node.extras.inferredType || "Object"; // will be filled in later
  			// need to remember the scope to place this function in for later
  			node.extras.scope = env.scope(node.extras.target);
  
  			// need to infer the body of this function later
  			node.body.extras.stop = true;
  		}
  
  		return true;
  	}
  
  	/**
  	 * called as the post operation for the proposalGenerator visitor.
  	 * Finishes off the inferencing and adds all proposals
  	 */
  	function inferencerPostOp(node, env) {
  		var type = node.type, name, inferredType, newTypeName, rightMost, kvps, i;
  
  		switch(type) {
  		case "Program":
  			if (env.defer) {
  				// finally, we can infer the deferred target function
  
  				// now use the comments that we deferred until later
  				env.comments = env.deferredComments;
  
  				var defer = env.defer;
  				env.defer = null;
  				env.targetType = null;
  				env.pushScope(defer.extras.scope);
  				mVisitor.visit(defer.body, env, inferencer, inferencerPostOp);
  				env.popScope();
  			}
  
  			// in case we haven't stored target yet, do so now.
  			env.storeTarget();
  
  			// TODO FIXADE for historical reasons we end visit by throwing exception.  Should chamge
  			throw env.targetType;
  		case "BlockStatement":
  		case "CatchClause":
  			if (inRange(env.offset, node.range)) {
  				// if we've gotten here and we are still in range, then
  				// we are completing as a top-level entity with no prefix
  				env.storeTarget();
  			}
  
  			env.popScope();
  			break;
  		case "MemberExpression":
  			if (afterDot(env.offset, node, env.contents)) {
  				// completion after a dot with no prefix
  				env.storeTarget(env.scope(node.object));
  			}
  
  			// for arrays, inferred type is the dereferncing of the array type
  			// for non-arrays inferred type is the type of the property expression
  			if (mTypes.isArrayType(node.object.extras.inferredType) && node.computed) {
  				// inferred type of expression is the type of the dereferenced array
  				node.extras.inferredType = mTypes.extractArrayParameterType(node.object.extras.inferredType);
  			} else if (node.computed && node.property && node.property.type !== "Literal") {
  				// we don't infer parameterized objects, but we have something like this: 'foo[at]'  just assume type is object
  				node.extras.inferredType = "Object";
  			} else {
  				// a regular member expression: foo.bar or foo['bar']
  				// node.propery will be null for mal-formed asts
  				node.extras.inferredType = node.property ? node.property.extras.inferredType : node.object.extras.inferredType;
  			}
  			break;
  		case "CallExpression":
  			// first check to see if this is a require call
  			var fnType = extractRequireModule(node, env);
  
  			// otherwise, apply the function
  			if (!fnType) {
  				fnType = node.callee.extras.inferredType;
  				fnType = mTypes.extractReturnType(fnType);
  			}
  			node.extras.inferredType = fnType;
  			break;
  		case "NewExpression":
  			// FIXADE we have a problem here.
  			// constructors that are called like this: new foo.Bar()  should have an inferred type of foo.Bar,
  			// This ensures that another constructor new baz.Bar() doesn't conflict.  However,
  			// we are only taking the final prefix and assuming that it is unique.
  			node.extras.inferredType = mTypes.extractReturnType(node.callee.extras.inferredType);
  			break;
  		case "ObjectExpression":
  			// now that we know all the types of the values, use that to populate the types of the keys
  			kvps = node.properties;
  			for (i = 0; i < kvps.length; i++) {
  				if (kvps[i].hasOwnProperty("key")) {
  					// only do this for keys that are identifiers
  					// set the proper inferred type for the key node
  					// and also update the variable
  					name = kvps[i].key.name;
  					if (name) {
  						// now check for the special case where the rhs value is an identifier.
  						// we want to shortcut the navigation and go through to the definition
  						// of the identifier, BUT only do this if the identifier points to a function
  						// and the key and value names match.
  						var range = null;
  						if (name === kvps[i].value.name) {
  							var def = env.lookupName(kvps[i].value.name, null, false, true);
  							if (def && def.range && (mTypes.isFunctionOrConstructor(def.typeName))) {
  								range = def.range;
  							}
  						}
  						if (!range) {
  							range = kvps[i].key.range;
  						}
  
  						inferredType = kvps[i].value.extras.inferredType;
  						var docComment = kvps[i].key.extras.associatedComment;
  						env.addVariable(name, node, inferredType, range, docComment.range);
  						if (inRange(env.offset-1, kvps[i].key.range)) {
  							// We found it! rmember for later, but continue to the end of file anyway
  							env.storeTarget(env.scope(node));
  						}
  					}
  				}
  			}
  			if (node.extras.fname) {
  				// this object expression is contained inside another object expression
  				env.popName();
  			}
  			env.popScope();
  			break;
  		case "LogicalExpression":
  		case "BinaryExpression":
  			switch (node.operator) {
  				case '+':
  					// special case: if either side is a string, then result is a string
  					if (node.left.extras.inferredType === "String" ||
  						node.right.extras.inferredType === "String") {
  
  						node.extras.inferredType = "String";
  					} else {
  						node.extras.inferredType = "Number";
  					}
  					break;
  				case '-':
  				case '/':
  				case '*':
  				case '%':
  				case '&':
  				case '|':
  				case '^':
  				case '<<':
  				case '>>':
  				case '>>>':
  					// Numeric and bitwise operations always return a number
  					node.extras.inferredType = "Number";
  					break;
  				case '&&':
  				case '||':
  					// will be the type of the left OR the right
  					// for now arbitrarily choose the left
  					node.extras.inferredType = node.left.extras.inferredType;
  					break;
  
  				case '!==':
  				case '!=':
  				case '===':
  				case '==':
  				case '<':
  				case '<=':
  				case '>':
  				case '>=':
  					node.extras.inferredType = "Boolean";
  					break;
  
  
  				default:
  					node.extras.inferredType = "Object";
  			}
  			break;
  		case "UpdateExpression":
  		case "UnaryExpression":
  			if (node.operator === '!') {
  				node.extras.inferredType = "Boolean";
  			} else {
  				// includes all unary operations and update operations
  				// ++ -- - and ~
  				node.extras.inferredType = "Number";
  			}
  			break;
  		case "FunctionDeclaration":
  		case "FunctionExpression":
  			env.popScope();
  			if (node.body) {
  				var fnameRange;
  				if (node.body.extras.isConstructor) {
  					if (node.id) {
  						fnameRange = node.id.range;
  					} else {
  						fnameRange = node.extras.fnameRange;
  					}
  
  					// an extra scope was created for the implicit 'this'
  					env.popScope();
  
  					// now add a reference to the constructor
  					env.addOrSetVariable(mTypes.extractReturnType(node.extras.inferredType), node.extras.target, node.extras.inferredType, fnameRange);
  				} else {
  					// a regular function.  if we don't already know the jsdoc return,
  					// try updating to a more explicit return type
  					if (!node.extras.jsdocReturn) {
  						var returnStatement = findReturn(node.body);
  						var returnType;
  						if (returnStatement && returnStatement.extras && returnStatement.extras.inferredType) {
  							returnType = returnStatement.extras.inferredType;
  						} else {
  							returnType = "undefined";
  						}
  						node.extras.inferredType = updateReturnType(node.extras.inferredType, returnType);
  					}
  					// if there is a name, then update that as well
  					var fname;
  					if (node.id) {
  						// true for function declarations
  						fname = node.id.name;
  						fnameRange = node.id.range;
  					} else if (node.extras.appliesTo) {
  						// true for rhs of assignment to function expression
  						fname = node.extras.fname;
  						fnameRange = node.extras.fnameRange;
  					}
  					if (fname) {
  						env.addOrSetVariable(fname, node.extras.target, node.extras.inferredType, fnameRange);
  					}
  				}
  			}
  			break;
  		case "VariableDeclarator":
  			if (node.init) {
  				inferredType = node.init.extras.inferredType;
  			} else {
  				inferredType = env.newFleetingObject();
  			}
  			node.id.extras.inferredType = inferredType;
  			if (!node.extras.jsdocType) {
  				node.extras.inferredType = inferredType;
  				env.addVariable(node.id.name, node.extras.target, inferredType, node.id.range, node.extras.docRange);
  			}
  			if (inRange(env.offset-1, node.id.range)) {
  				// We found it! rmember for later, but continue to the end of file anyway
  				env.storeTarget(env.scope(node.id.extras.target));
  			}
  			env.popName();
  			break;
  
  		case "Property":
  			node.extras.inferredType = node.key.extras.inferredType = node.value.extras.inferredType;
  			break;
  
  		case "AssignmentExpression":
  			if (node.extras.jsdocType) {
  				// use jsdoc instead of whatever we have inferred
  				inferredType = node.extras.jsdocType;
  			} else if (node.operator === '=') {
  				// standard assignment
  				inferredType = node.right.extras.inferredType;
  			} else {
  				// +=, -=, *=, /=, >>=, <<=, >>>=, &=, |=, or ^=.
  				if (node.operator === '+=' && node.left.extras.inferredType === 'String') {
  					inferredType = "String";
  				} else {
  					inferredType = "Number";
  				}
  			}
  
  			node.extras.inferredType = inferredType;
  			// when we have 'this.that.theOther.f' need to find the right-most identifier
  			rightMost = findRightMost(node.left);
  			if (rightMost && (rightMost.type === "Identifier" || rightMost.type === "Literal")) {
  				name = rightMost.name ? rightMost.name : rightMost.value;
  				rightMost.extras.inferredType = inferredType;
  				env.addOrSetVariable(name, rightMost.extras.target, inferredType, rightMost.range, node.extras.docRange);
  				if (inRange(env.offset-1, rightMost.range)) {
  					// We found it! remember for later, but continue to the end of file anyway
  					env.storeTarget(env.scope(rightMost.extras.target));
  				}
  			} else {
  				// might be an assignment to an array, like:
  				//   foo[at] = bar;
  				if (node.left.computed) {
  					rightMost = findRightMost(node.left.object);
  					if (rightMost && !(rightMost.type === 'Identifier' && rightMost.name === 'prototype')) {
  						// yep...now go and update the type of the array
  						// (also don't turn refs to prototype into an array. this breaks things)
  						var arrayType = mTypes.parameterizeArray(inferredType);
  						node.left.extras.inferredType = inferredType;
  						node.left.object.extras.inferredType = arrayType;
  						env.addOrSetVariable(rightMost.name, rightMost.extras.target, arrayType, rightMost.range, node.extras.docRange);
  					}
  				}
  			}
  			env.popName();
  			break;
  		case 'Identifier':
  			name = node.name;
  			newTypeName = env.lookupName(name, node.extras.target);
  			if (newTypeName && !node.extras.isDecl) {
  				// name already exists but we are redeclaring it and so not being overridden
  				node.extras.inferredType = newTypeName;
  				if (inRange(env.offset, node.range, true)) {
  					// We found it! rmember for later, but continue to the end of file anyway
  					env.storeTarget(env.scope(node.extras.target));
  				}
  			} else if (!node.extras.isLHS) {
  				if (!inRange(env.offset, node.range, true) && !isReserverdWord(name)) {
  					// we have encountered a read of a variable/property that we have never seen before
  
  					if (node.extras.target) {
  						// this is a property on an object.  just add to the target
  						env.addVariable(name, node.extras.target, env.newFleetingObject(), node.range);
  					} else {
  						// add as a global variable
  						node.extras.inferredType = env.addOrSetGlobalVariable(name, null, node.range).typeName;
  					}
  				} else {
  					// We found it! rmember for later, but continue to the end of file anyway
  					env.storeTarget(env.scope(node.extras.target));
  				}
  			} else {
  				// if this node is an LHS of an assign, don't store target yet,
  				// we need to first apply the RHS before applying.
  				// This will happen in the enclosing assignment or variable declarator
  			}
  			break;
  		case "ThisExpression":
  			node.extras.inferredType = env.lookupName("this");
  			if (inRange(env.offset-1, node.range)) {
  				// We found it! rmember for later, but continue to the end of file anyway
  				env.storeTarget(env.scope());
  			}
  			break;
  		case "ReturnStatement":
  			if (node.argument) {
  				node.extras.inferredType = node.argument.extras.inferredType;
  			}
  			break;
  
  		case "Literal":
  			if (node.extras.target && typeof node.value === "string") {
  				// we are inside a computed member expression.
  				// find the type of the property referred to if exists
  				name = node.value;
  				newTypeName = env.lookupName(name, node.extras.target);
  				node.extras.inferredType = newTypeName;
  			} else if (node.extras.target && typeof node.value === "number") {
  				// inside of an array access
  				node.extras.inferredType = "Number";
  			} else {
  				var oftype = (typeof node.value);
  				node.extras.inferredType = oftype[0].toUpperCase() + oftype.substring(1, oftype.length);
  			}
  			break;
  
  		case "ConditionalExpression":
  			var target = node.consequent ? node.consequent : node.alternate;
  			if (target) {
  				node.extras.inferredType = target.extras.inferredType;
  			}
  			break;
  
  		case "ArrayExpression":
  			// parameterize this array by the type of its first non-null element
  			if (node.elements) {
  				for (i = 0; i < node.elements.length; i++) {
  					if (node.elements[i]) {
  						node.extras.inferredType = mTypes.parameterizeArray(node.elements[i].extras.inferredType);
  					}
  				}
  			}
  		}
  
  		if (!node.extras.inferredType) {
  			node.extras.inferredType = "Object";
  		}
  	}
  
  
  	/**
  	 * add variable names from inside a lint global directive
  	 */
  	function addLintGlobals(env, lintOptions) {
  		var i, globName;
  		if (lintOptions && isArray(lintOptions.global)) {
  			for (i = 0; i < lintOptions.global.length; i++) {
  				globName = lintOptions.global[i];
  				if (!env.lookupName(globName)) {
  					env.addOrSetVariable(globName);
  				}
  			}
  		}
  		var comments = env.comments;
  		if (comments) {
  			for (i = 0; i < comments.length; i++) {
  				var range = comments[i].range;
  				if (comments[i].type === "Block" && comments[i].value.substring(0, "global".length) === "global") {
  					var globals = comments[i].value;
  					var splits = globals.split(/\s+/);
  					// start with 1 to avoid 'global'
  					for (var j = 1; j < splits.length; j++) {
  						if (splits[j].length > 0) {
  							var colonIdx = splits[j].indexOf(':');
  							if (colonIdx >= 0) {
  								globName = splits[j].substring(0,colonIdx).trim();
  							} else {
  								globName = splits[j].trim();
  							}
  							if (!env.lookupName(globName)) {
  								env.addOrSetVariable(globName, null, null, range);
  							}
  						}
  					}
  					break;
  				}
  			}
  		}
  	}
  
  	/**
  	 * Adds global variables defined in dependencies
  	 */
  	function addIndexedGlobals(env) {
  		// no indexer means that we should not consult indexes for extra type information
  		if (env.indexer) {
  			// get the list of summaries relevant for this file
  			// add it to the global scope
  			var summaries = env.indexer.retrieveGlobalSummaries();
  			for (var fileName in summaries) {
  				if (summaries.hasOwnProperty(fileName)) {
  					env.mergeSummary(summaries[fileName], env.globalTypeName());
  				}
  			}
  		}
  	}
  
  	/**
  	 * the prefix of a completion should not be included in the completion itself
  	 * must explicitly remove it
  	 */
  	function removePrefix(prefix, string) {
  		return string.substring(prefix.length);
  	}
  
  	/**
  	 * Determines if the left type name is more general than the right type name.
  	 * Generality (>) is defined as follows:
  	 * undefined > Object > Generated empty type > all other types
  	 *
  	 * A generated empty type is a generated type that has only a $$proto property
  	 * added to it.  Additionally, the type specified in the $$proto property is
  	 * either empty or is Object
  	 *
  	 * @param String leftTypeName
  	 * @param String rightTypeName
  	 * @param {{getAllTypes:function():Object}} env
  	 *
  	 * @return Boolean
  	 */
  	function leftTypeIsMoreGeneral(leftTypeName, rightTypeName, env) {
  		function isEmpty(generatedTypeName) {
  			if (generatedTypeName === "Object" || generatedTypeName === "undefined") {
  				return true;
  			} else if (leftTypeName.substring(0, mTypes.GEN_NAME.length) !== mTypes.GEN_NAME) {
  				return false;
  			}
  
  
  			var type = env.getAllTypes()[generatedTypeName];
  			var popCount = 0;
  			// type should have a $$proto only and nothing else if it is empty
  			for (var property in type) {
  				if (type.hasOwnProperty(property)) {
  					popCount++;
  					if (popCount > 1) {
  						break;
  					}
  				}
  			}
  			if (popCount === 1) {
  				// we have an empty object literal, must check parent
  				// must traverse prototype hierarchy to make sure empty
  				return isEmpty(type.$$proto.typeName);
  			}
  			return false;
  		}
  
  		function convertToNumber(typeName) {
  			if (typeName === "undefined") {
  				return 0;
  			} else if (typeName === "Object") {
  				return 1;
  			} else if (isEmpty(typeName)) {
  				return 2;
  			} else {
  				return 3;
  			}
  		}
  
  		if (!rightTypeName) {
  			return false;
  		}
  
  		var leftNum = convertToNumber(leftTypeName);
  		// avoid calculating the rightNum if possible
  		if (leftNum === 0) {
  			return rightTypeName !== "undefined";
  		} else if (leftNum === 1) {
  			return rightTypeName !== "undefined" && rightTypeName !== "Object";
  		} else if (leftNum === 2) {
  			return rightTypeName !== "undefined" && rightTypeName !== "Object" && !isEmpty(rightTypeName);
  		} else {
  			return false;
  		}
  	}
  
  	/**
  	 * @return boolean true iff the type contains
  	 * prop.  prop must not be coming from Object
  	 */
  	function typeContainsProperty(type, prop) {
  		if (! (prop in type)) {
  			return false;
  		}
  		if (Object.hasOwnProperty(prop)) {
  			// the propery may be re-defined in the current type
  			// check that here
  			return !type.hasOwnProperty(prop);
  		}
  		return true;
  	}
  
  	/**
  	 * Creates the environment object that stores type information
  	 * Called differently depending on what job this content assistant is being called to do.
  	 */
  	function createEnvironment(options) {
  		var buffer = options.buffer, uid = options.uid, offset = options.offset, indexer = options.indexer, globalObjName = options.globalObjName;
  		if (!offset) {
  			offset = buffer.length+1;
  		}
  
  		// must copy comments because the array is mutable
  		var comments = [];
  		if (options.comments) {
  			for (var i = 0; i < options.comments.length; i++) {
  				comments[i] = options.comments[i];
  			}
  		}
  
  		// prefix for generating local types
  		// need to add a unique id for each file so that types defined in dependencies don't clash with types
  		// defined locally
  		var namePrefix = mTypes.GEN_NAME + uid + "~";
  
  		return {
  			/** Each element is the type of the current scope, which is a key into the types array */
  			_scopeStack : [globalObjName],
  			/**
  			 * a map of all the types and their properties currently known
  			 * when an indexer exists, local storage will be checked for extra type information
  			 */
  			_allTypes : new mTypes.Types(globalObjName),
  			/** a counter used for creating unique names for object literals and scopes */
  			_typeCount : 0,
  
  			_nameStack : [],
  
  			/** if this is an AMD module, then the value of this property is the 'define' call expression */
  			amdModule : null,
  			/** if this is a wrapped commonjs module, then the value of this property is the 'define' call expression */
  			commonjsModule : null,
  			/** the indexer for thie content assist invocation.  Used to track down dependencies */
  			indexer: indexer,
  			/** the offset of content assist invocation */
  			offset : offset,
  			/** the entire contents being completed on */
  			contents : buffer,
  			uid : uid === 'local' ? null : uid,
  
  			/** List of comments in the AST*/
  			comments : comments,
  
  			newName: function() {
  				return namePrefix + this._typeCount++;
  			},
  			/**
  			 * Creates a new empty scope and returns the name of the scope
  			 * must call this.popScope() when finished with this scope
  			 */
  			newScope: function(range) {
  				// the prototype is always the currently top level scope
  				var targetType = this.scope();
  				var newScopeName = this.newName();
  				this._allTypes[newScopeName] = {
  					$$proto : new mTypes.Definition(targetType, range, this.uid)
  				};
  				this._scopeStack.push(newScopeName);
  				return newScopeName;
  			},
  
  			pushScope : function(scopeName) {
  				this._scopeStack.push(scopeName);
  			},
  
  			pushName : function(name) {
  				this._nameStack.push(name);
  			},
  
  			popName : function() {
  				this._nameStack.pop();
  			},
  
  			getQualifiedName : function() {
  				var name = this._nameStack.join('.');
  				return name.length > 0 ? name + '.' : name;
  			},
  
  			/**
  			 * Creates a new empty object scope and returns the name of this object
  			 * must call this.popScope() when finished
  			 */
  			newObject: function(newObjectName, range) {
  				// object needs its own scope
  				this.newScope();
  				// if no name passed in, create a new one
  				newObjectName = newObjectName? newObjectName : this.newName();
  				// assume that objects have their own "this" object
  				// prototype of Object
  				this._allTypes[newObjectName] = {
  					$$proto : new mTypes.Definition("Object", range, this.uid)
  				};
  				this.addVariable("this", null, newObjectName, range);
  
  				return newObjectName;
  			},
  
  			/**
  			 * like a call to this.newObject(), but the
  			 * object created has not scope added to the scope stack
  			 */
  			newFleetingObject : function(name, range) {
  				var newObjectName = name ? name : this.newName();
  				this._allTypes[newObjectName] = {
  					$$proto : new mTypes.Definition("Object", range, this.uid)
  				};
  				return newObjectName;
  			},
  
  			/** removes the current scope */
  			popScope: function() {
  				// Can't delete old scope since it may have been assigned somewhere
  				var oldScope = this._scopeStack.pop();
  				return oldScope;
  			},
  
  			/**
  			 * @param {ASTNode|String} target
  			 * returns the type name for the current scope
  			 * if a target is passed in (optional), then use the
  			 * inferred type of the target instead (if it exists)
  			 */
  			scope : function(target) {
  				if (typeof target === "string") {
  					return target;
  				}
  
  				if (target && target.extras.inferredType) {
  					// check for function literal
  					var inferredType = target.extras.inferredType;
  					// hmmmm... will be a problem here if there are nested ~protos
  					if (mTypes.isFunctionOrConstructor(inferredType) && !mTypes.isPrototype(inferredType)) {
  						var noArgsType = mTypes.removeParameters(inferredType);
  						if (this._allTypes[noArgsType]) {
  							return noArgsType;
  						} else {
  							return "Function";
  						}
  					} else if (mTypes.isArrayType(inferredType)) {
  						// TODO FIXADE we are losing parameterization here
  						return "Array";
  					} else {
  						return inferredType;
  					}
  				} else {
  					// grab topmost scope
  					return this._scopeStack[this._scopeStack.length -1];
  				}
  			},
  
  			globalScope : function() {
  				return this._allTypes[this._scopeStack[0]];
  			},
  
  			globalTypeName : function() {
  				return this._scopeStack[0];
  			},
  
  			/**
  			 * adds the name to the target type.
  			 * if target is passed in then use the type corresponding to
  			 * the target, otherwise use the current scope
  			 *
  			 * Will not override an existing variable if the new typeName is "Object" or "undefined"
  			 * Will not add to a built in type
  			 *
  			 * @param {String} name
  			 * @param {String} typeName
  			 * @param {Object} target
  			 * @param {Array.<Number>} range
  			 * @param {Array.<Number>} docRange
  			 */
  			addVariable : function(name, target, typeName, range, docRange) {
  				if (this._allTypes.Object["$_$" + name]) {
  					// this is a built in property of object.  do not redefine
  					return;
  				}
  				var type = this._allTypes[this.scope(target)];
  				// do not allow augmenting built in types
  				if (!type.$$isBuiltin) {
  					// if new type name is not more general than old type, do not replace
  					if (typeContainsProperty(type, name) && leftTypeIsMoreGeneral(typeName, type[name].typeName, this)) {
  						// do nuthin
  					} else {
  						type[name] = new mTypes.Definition(typeName ? typeName : "Object", range, this.uid);
  						type[name].docRange = docRange;
  						return type[name];
  					}
  				}
  			},
  
  			addOrSetGlobalVariable : function(name, typeName, range, docRange) {
  				if (this._allTypes.Object["$_$" + name]) {
  					// this is a built in property of object.  do not redefine
  					return;
  				}
  				return this.addOrSetVariable(name,
  					// mock an ast node with a global type
  					{ extras : { inferredType : this.globalTypeName() } }, typeName, range, docRange);
  			},
  
  			/**
  			 * like add variable, but first checks the prototype hierarchy
  			 * if exists in prototype hierarchy, then replace the type
  			 *
  			 * Will not override an existing variable if the new typeName is "Object" or "undefined"
  			 */
  			addOrSetVariable : function(name, target, typeName, range, docRange) {
  				if (name === 'prototype') {
  					name = '$$proto';
  				} else if (this._allTypes.Object["$_$" + name]) {
  					// this is a built in property of object.  do not redefine
  					return;
  				}
  
  				var targetType = this.scope(target);
  				var current = this._allTypes[targetType], found = false;
  				// if no type provided, create a new type
  				typeName = typeName ? typeName : this.newFleetingObject();
  				var defn;
  				while (current) {
  					if (typeContainsProperty(current, name)) {
  						defn = current[name];
  						// found it, just overwrite
  						// do not allow overwriting of built in types
  						// 3 cases to avoid:
  						//  1. properties of builtin types cannot be set
  						//  2. builtin types cannot be redefined
  						//  3. new type name is more general than old type
  						if (!current.$$isBuiltin && current.hasOwnProperty(name) &&
  								!leftTypeIsMoreGeneral(typeName, defn.typeName, this)) {
  							// since we are just overwriting the type we do not want to change
  							// the path or the range
  							defn.typeName = typeName;
  							if (docRange) {
  								defn.docRange = docRange;
  							}
  						}
  						found = true;
  						break;
  					} else if (current.$$proto) {
  						current = this._allTypes[current.$$proto.typeName];
  					} else {
  						current = null;
  					}
  				}
  
  				if (!found) {
  					// not found, so just add to current scope
  					// do not allow overwriting of built in types
  					var type = this._allTypes[targetType];
  					if (!type.$$isBuiltin) {
  						defn = new mTypes.Definition(typeName, range, this.uid);
  						defn.docRange = docRange;
  						type[name] = defn;
  					}
  				}
  				return defn;
  			},
  
  			/** looks up the name in the hierarchy */
  			lookupName : function(name, target, applyFunction, includeDefinition) {
  
  				// translate function names on object into safe names
  				var swapper = function(name) {
  					switch (name) {
  						case "prototype":
  							return "$$proto";
  						case "toString":
  						case "hasOwnProperty":
  						case "toLocaleString":
  						case "valueOf":
  						case "isProtoTypeOf":
  						case "propertyIsEnumerable":
  							return "$_$" + name;
  						default:
  							return name;
  					}
  				};
  
  				var innerLookup = function(name, type, allTypes) {
  					var res = type[name];
  
  					var proto = type.$$proto;
  					if (res) {
  						return includeDefinition ? res : res.typeName;
  					} else if (proto) {
  						return innerLookup(name, allTypes[proto.typeName], allTypes);
  					} else {
  						return null;
  					}
  				};
  				var targetType = this._allTypes[this.scope(target)];
  
  				// uncomment this if we want to hide errors where there is an unknown type being placed on the scope stack
  //				if (!targetType) {
  //					targetType = this.globalScope()
  //				}
  				var res = innerLookup(swapper(name), targetType, this._allTypes);
  				return res;
  			},
  
  			/** removes the variable from the current type */
  			removeVariable : function(name, target) {
  				// do not allow deleting properties of built in types
  				var type = this._allTypes[this.scope(target)];
  				// 2 cases to avoid:
  				//  1. properties of builtin types cannot be deleted
  				//  2. builtin types cannot be deleted from global scope
  				if (!type.$$isBuiltin && type[name] && !(type[name] && !type.hasOwnProperty(name))) {
  					delete type[name];
  				}
  			},
  
  			/**
  			 * adds a file summary to this module
  			 */
  			mergeSummary : function(summary, targetTypeName) {
  
  				// add the extra types that don't already exists
  				for (var type in summary.types) {
  					if (summary.types.hasOwnProperty(type) && !this._allTypes[type]) {
  						this._allTypes[type] = summary.types[type];
  					}
  				}
  
  				// now augment the target type with the provided properties
  				// but only if a composite type is exported
  				var targetType = this._allTypes[targetTypeName];
  				if (typeof summary.provided !== 'string') {
  					for (var providedProperty in summary.provided) {
  						if (summary.provided.hasOwnProperty(providedProperty)) {
  							// the targetType may already have the providedProperty defined
  							// but should override
  							targetType[providedProperty] = summary.provided[providedProperty];
  						}
  					}
  				}
  			},
  
  			/**
  			 * takes the name of a constructor and converts it into a type.
  			 * We need to ensure that ConstructorName.prototype = { ... } does the
  			 * thing that we expect.  This is why we set the $$proto property of the types
  			 */
  			createConstructor : function(constructorName, rawTypeName) {
  				// don't include the parameter names since we don't want them confusing things when exported
  				constructorName = mTypes.removeParameters(constructorName);
  				this.newFleetingObject(constructorName);
  				var flobj = this.newFleetingObject(constructorName + "~proto");
  				this._allTypes[constructorName].$$proto = new mTypes.Definition(flobj, null, this.uidj);
  				this._allTypes[rawTypeName].$$proto = new mTypes.Definition(constructorName, null, this.uid);
  			},
  
  			findType : function(typeName) {
  				if (mTypes.isArrayType(typeName)) {
  					// TODO is there anything we need to do here?
  					// parameterized array
  					typeName = "Array";
  				}
  
  				// trim arguments if a constructor, careful to avoid a constructor prototype
  				if (typeName.charAt(0) === "?") {
  					typeName = mTypes.removeParameters(typeName);
  
  					if (!this._allTypes[typeName]) {
  						// function type has not been explicitly added to list
  						// just return function instead
  						return this._allTypes.Function;
  					}
  				}
  				return this._allTypes[typeName];
  			},
  
  			getAllTypes : function() {
  				return this._allTypes;
  			},
  
  			/**
  			 * This function stores the target type
  			 * so it can be used as the result of this inferencing operation
  			 */
  			storeTarget : function(targetType) {
  				if (!this.targetType) {
  					if (!targetType) {
  						targetType = this.scope();
  					}
  					this.targetType = targetType;
  					this.targetFound = true;
  				}
  			}
  		};
  	}
  
  	function createProposalDescription(propName, propType, env) {
  		return propName + " : " + mTypes.createReadableType(propType, env);
  	}
  
  	function createInferredProposals(targetTypeName, env, completionKind, prefix, replaceStart, proposals, relevance) {
  		var prop, propName, propType, res, type = env.findType(targetTypeName), proto = type.$$proto;
  		if (!relevance) {
  			relevance = 100;
  		}
  		// start at the top of the prototype hierarchy so that duplicates can be removed
  		if (proto) {
  			createInferredProposals(proto.typeName, env, completionKind, prefix, replaceStart, proposals, relevance - 10);
  		}
  
  		// add a separator proposal
  		proposals['---dummy' + relevance] = {
  			proposal: '',
  			description: '---------------------------------',
  			relevance: relevance -1,
  			style: 'hr',
  			unselectable: true
  		};
  
  		// need to look at prototype for global and window objects
  		// so need to traverse one level up prototype hierarchy if
  		// the next level is not Object
  		var realProto = Object.getPrototypeOf(type);
  		var protoIsObject = !Object.getPrototypeOf(realProto);
  		for (prop in type) {
  			if (type.hasOwnProperty(prop) || (!protoIsObject && realProto.hasOwnProperty(prop))) {
  				if (prop.charAt(0) === "$" && prop.charAt(1) === "$") {
  					// special property
  					continue;
  				}
  				if (!proto && prop.indexOf("$_$") === 0) {
  					// no prototype that means we must decode the property name
  					propName = prop.substring(3);
  				} else {
  					propName = prop;
  				}
  				if (propName === "this" && completionKind === "member") {
  					// don't show "this" proposals for non-top-level locations
  					// (eg- this.this is wrong)
  					continue;
  				}
  				if (!type[prop].typeName) {
  					// minified files sometimes have invalid property names (eg- numbers).  Ignore them)
  					continue;
  				}
  				if (proposalUtils.looselyMatches(prefix, propName)) {
  					propType = type[prop].typeName;
  					var first = propType.charAt(0);
  					if (first === "?" || first === "*") {
  						// we have a function
  						res = calculateFunctionProposal(propName,
  								propType, replaceStart - 1);
  						var funcDesc = res.completion + " : " + mTypes.createReadableType(propType, env);
  						proposals["$"+propName] = {
  							proposal: removePrefix(prefix, res.completion),
  							description: funcDesc,
  							positions: res.positions,
  							escapePosition: replaceStart + res.completion.length,
  							// prioritize methods over fields
  							relevance: relevance + 5,
  							style: 'emphasis'
  						};
  					} else {
  						proposals["$"+propName] = {
  							proposal: removePrefix(prefix, propName),
  							relevance: relevance,
  							description: createProposalDescription(propName, propType, env),
  							style: 'emphasis'
  						};
  					}
  				}
  			}
  		}
  	}
  
  	function createNoninferredProposals(environment, prefix, replaceStart, proposals) {
  		var proposalAdded = false;
  		// a property to return is one that is
  		//  1. defined on the type object
  		//  2. prefixed by the prefix
  		//  3. doesn't already exist
  		//  4. is not an internal property
  		function isInterestingProperty(type, prop) {
  			return type.hasOwnProperty(prop) && prop.indexOf(prefix) === 0 && !proposals['$' + prop] && prop !== '$$proto'&& prop !== '$$isBuiltin';
  		}
  		function forType(type) {
  			for (var prop in type) {
  				if (isInterestingProperty(type, prop)) {
  					var propType = type[prop].typeName;
  					var first = propType.charAt(0);
  					if (first === "?" || first === "*") {
  						var res = calculateFunctionProposal(prop, propType, replaceStart - 1);
  						proposals[prop] = {
  							proposal: removePrefix(prefix, res.completion),
  							description: createProposalDescription(prop, propType, environment),
  							positions: res.positions,
  							escapePosition: replaceStart + res.completion.length,
  							// prioritize methods over fields
  							relevance: -99,
  							style: 'noemphasis'
  						};
  						proposalAdded = true;
  					} else {
  						proposals[prop] = {
  							proposal: removePrefix(prefix, prop),
  							description: createProposalDescription(prop, propType, environment),
  							relevance: -100,
  							style: 'noemphasis'
  						};
  						proposalAdded = true;
  					}
  				}
  			}
  		}
  		var allTypes = environment.getAllTypes();
  		for (var typeName in allTypes) {
  			// need to traverse into the prototype
  			if (allTypes[typeName].$$proto) {
  				forType(allTypes[typeName]);
  			}
  		}
  
  		if (proposalAdded) {
  			proposals['---dummy'] = {
  				proposal: '',
  				description: 'Non-inferred proposals',
  				relevance: -98,
  				style: 'noemphasis',
  				unselectable: true
  			};
  		}
  	}
  
  	function findUnreachable(currentTypeName, allTypes, alreadySeen) {
  		if (currentTypeName.charAt(0) === '*') {
  			// constructors are not stored with their arguments so need to remove them in order to find them
  			currentTypeName = mTypes.removeParameters(currentTypeName);
  		}
  		var currentType = allTypes[currentTypeName];
  		if (currentType) {
  			for(var prop in currentType) {
  				if (currentType.hasOwnProperty(prop) && prop !== '$$isBuiltin' ) {
  					var propType = currentType[prop].typeName;
  					while (mTypes.isFunctionOrConstructor(propType) || mTypes.isArrayType(propType)) {
  						if (!alreadySeen[propType]) {
  							alreadySeen[propType] = true;
  							findUnreachable(propType, allTypes, alreadySeen);
  						}
  						if (mTypes.isFunctionOrConstructor(propType)) {
  							propType = mTypes.extractReturnType(propType);
  						} else if (mTypes.isArrayType(propType)) {
  							propType = mTypes.extractArrayParameterType(propType);
  						}
  					}
  					if (!alreadySeen[propType]) {
  						alreadySeen[propType] = true;
  						findUnreachable(propType, allTypes, alreadySeen);
  					}
  				}
  			}
  		}
  	}
  
  	/**
  	 * filters types from the environment that should not be exported
  	 */
  	function filterTypes(environment, kind, moduleTypeName) {
  		var allTypes = environment.getAllTypes();
  		if (kind === "global") {
  			// for global dependencies must keep the global scope, but remove all builtin global variables
  			allTypes.clearDefaultGlobal();
  		} else {
  			delete allTypes.Global;
  		}
  
  		// recursively walk the type tree to find unreachable types and delete them, too
  		var reachable = { };
  		// if we have a function, then the function return type and its prototype are reachable
  		// also do same if parameterized array type
  		// in the module, so add them
  		if (mTypes.isFunctionOrConstructor(moduleTypeName) || mTypes.isArrayType(moduleTypeName)) {
  			var retType = moduleTypeName;
  			while (mTypes.isFunctionOrConstructor(retType) || mTypes.isArrayType(retType)) {
  				if (mTypes.isFunctionOrConstructor(retType)) {
  					retType = mTypes.removeParameters(retType);
  					reachable[retType] = true;
  					var constrType;
  					if (retType.charAt(0) === "?") {
  						// this is a function, not a constructor, but we also
  						// need to expose the constructor if one exists.
  						constrType = "*" + retType.substring(1);
  						reachable[constrType] = true;
  					} else {
  						constrType = retType;
  					}
  					// don't strictly need this if the protoype of the object has been changed, but OK to keep
  					reachable[constrType + "~proto"] = true;
  					retType = mTypes.extractReturnType(retType);
  				} else if (mTypes.isArrayType(retType)) {
  					retType = mTypes.extractArrayParameterType(retType);
  					if (retType) {
  						reachable[retType] = true;
  					} else {
  						retType = "Object";
  					}
  				}
  			}
  			reachable[retType] = true;
  		}
  
  		findUnreachable(moduleTypeName, allTypes, reachable);
  		for (var prop in allTypes) {
  			if (allTypes.hasOwnProperty(prop) && !reachable[prop]) {
  				delete allTypes[prop];
  			}
  		}
  	}
  
  	var browserRegExp = /browser\s*:\s*true/;
  	var nodeRegExp = /node\s*:\s*true/;
  	function findGlobalObject(comments, lintOptions) {
  
  		for (var i = 0; i < comments.length; i++) {
  			var comment = comments[i];
  			if (comment.type === "Block" && (comment.value.substring(0, "jslint".length) === "jslint" ||
  											  comment.value.substring(0,"jshint".length) === "jshint")) {
  				// the lint options section.  now look for the browser or node
  				if (comment.value.match(browserRegExp)) {
  					return "Window";
  				} else if (comment.value.match(nodeRegExp)) {
  					return "Module";
  				} else {
  					return "Global";
  				}
  			}
  		}
  		if (lintOptions && lintOptions.options) {
  			if (lintOptions.options.browser === true) {
  				return "Window";
  			} else if (lintOptions.options.node === true) {
  				return "Module";
  			}
  		}
  		return "Global";
  	}
  
  	function filterAndSortProposals(proposalsObj) {
  		// convert from object to array
  		var proposals = [];
  		for (var prop in proposalsObj) {
  			if (proposalsObj.hasOwnProperty(prop)) {
  				proposals.push(proposalsObj[prop]);
  			}
  		}
  		proposals.sort(function(l,r) {
  			// sort by relevance and then by name
  			if (l.relevance > r.relevance) {
  				return -1;
  			} else if (r.relevance > l.relevance) {
  				return 1;
  			}
  
  			var ldesc = l.description.toLowerCase();
  			var rdesc = r.description.toLowerCase();
  			if (ldesc < rdesc) {
  				return -1;
  			} else if (rdesc < ldesc) {
  				return 1;
  			}
  			return 0;
  		});
  
  		// filter trailing and leading dummies, as well as double dummies
  		var toRemove = [];
  
  		// now remove any leading or trailing dummy proposals as well as double dummies
  		var i = proposals.length -1;
  		while (i >= 0 && proposals[i].description.indexOf('---') === 0) {
  			toRemove[i] = true;
  			i--;
  		}
  		i = 0;
  		while (i < proposals.length && proposals[i].description.indexOf('---') === 0) {
  			toRemove[i] = true;
  			i++;
  		}
  		i += 1;
  		while (i < proposals.length) {
  			if (proposals[i].description.indexOf('---') === 0 && proposals[i-1].description.indexOf('---') === 0) {
  				toRemove[i] = true;
  			}
  			i++;
  		}
  
  		var newProposals = [];
  		for (i = 0; i < proposals.length; i++) {
  			if (!toRemove[i]) {
  				newProposals.push(proposals[i]);
  			}
  		}
  
  		return newProposals;
  	}
  
  
  	/**
  	 * indexer is optional.  When there is no indexer passed in
  	 * the indexes will not be consulted for extra references
  	 * @param {{hasDependency,performIndex,retrieveSummary,retrieveGlobalSummaries}} indexer
  	 * @param {{global:[],options:{browser:Boolean}}=} lintOptions optional set of extra lint options that can be overridden in the source (jslint or jshint)
  	 */
  	function EsprimaJavaScriptContentAssistProvider(indexer, lintOptions) {
  		this.indexer = indexer;
  		this.lintOptions = lintOptions;
  	}
  
  	/**
  	 * Main entry point to provider
  	 */
  	EsprimaJavaScriptContentAssistProvider.prototype = {
  
  		_doVisit : function(root, environment) {
  			// first augment the global scope with things we know
  			addLintGlobals(environment, this.lintOptions);
  			addIndexedGlobals(environment);
  
  			// now we can remove all non-doc comments from the comments list
  			var newComments = [];
  			for (var i = 0; i < environment.comments.length; i++) {
  				if (environment.comments[i].value.charAt(0) === '*') {
  					newComments.push(environment.comments[i]);
  				}
  			}
  			environment.comments = newComments;
  
  			try {
  				mVisitor.visit(root, environment, inferencer, inferencerPostOp);
  			} catch (done) {
  				if (typeof done !== "string") {
  					// a real error
  					throw done;
  				}
  				return done;
  			}
  			throw new Error("The visit function should always end with a throwable");
  		},
  
  		/**
  		 * implements the Orion content assist API
  		 */
  		computeProposals: function(buffer, offset, context) {
  			if (context.selection && context.selection.start !== context.selection.end) {
  				// only propose if an empty selection.
  				return null;
  			}
  
  			try {
  				var root = mVisitor.parse(buffer);
  				if (!root) {
  					// assume a bad parse
  					return null;
  				}
  				// note that if selection has length > 0, then just ignore everything past the start
  				var completionKind = shouldVisit(root, offset, context.prefix, buffer);
  				if (completionKind) {
  					var environment = createEnvironment({ buffer: buffer, uid : "local", offset : offset, indexer : this.indexer, globalObjName : findGlobalObject(root.comments, this.lintOptions), comments : root.comments });
  					// must defer inferring the containing function block until the end
  					environment.defer = completionKind.toDefer;
  					if (environment.defer) {
  						// remove these comments from consideration until we are inferring the deferred
  						environment.deferredComments = extractDocComments(environment.comments, environment.defer.range);
  					}
  					var target = this._doVisit(root, environment);
  					var proposalsObj = { };
  					createInferredProposals(target, environment, completionKind.kind, context.prefix, offset - context.prefix.length, proposalsObj);
  					if (false && !context.inferredOnly) {
  						// include the entire universe as potential proposals
  						createNoninferredProposals(environment, context.prefix, offset - context.prefix.length, proposalsObj);
  					}
  					return filterAndSortProposals(proposalsObj);
  				} else {
  					// invalid completion location
  					return [];
  				}
  			} catch (e) {
  				if (typeof scriptedLogger !== "undefined") {
  					scriptedLogger.error(e.message, "CONTENT_ASSIST");
  					scriptedLogger.error(e.stack, "CONTENT_ASSIST");
  				}
  				throw (e);
  			}
  		},
  
  
  		_internalFindDefinition : function(buffer, offset, findName) {
  			var toLookFor;
  			var root = mVisitor.parse(buffer);
  			if (!root) {
  				// assume a bad parse
  				return null;
  			}
  			var funcList = [];
  			var environment = createEnvironment({ buffer: buffer, uid : "local", offset : offset, indexer : this.indexer, globalObjName : findGlobalObject(root.comments, this.lintOptions), comments : root.comments });
  			var findIdentifier = function(node) {
  				if ((node.type === "Identifier" || node.type === "ThisExpression") && inRange(offset, node.range, true)) {
  					toLookFor = node;
  					// cut visit short
  					throw "done";
  				}
  				// FIXADE esprima bug...some call expressions have incorrect slocs.
  				// This is fixed in trunk of esprima.
  				// after next upgrade of esprima if the following has correct slocs, then
  				// can remove the second part of the &&
  				//    mUsers.getUser().name
  				if (node.range[0] > offset &&
  						(node.type === "ExpressionStatement" ||
  						 node.type === "ReturnStatement" ||
  						 node.type === "ifStatement" ||
  						 node.type === "WhileStatement" ||
  						 node.type === "Program")) {
  					// not at a valid hover location
  					throw "no hover";
  				}
  
  				// the last function pushed on is the one that we need to defer
  				if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
  					funcList.push(node);
  				}
  				return true;
  			};
  
  			try {
  				mVisitor.visit(root, {}, findIdentifier, function(node) {
  					if (node === funcList[funcList.length-1]) {
  						funcList.pop();
  					}
  				});
  			} catch (e) {
  				if (e === "no hover") {
  					// not at a valid hover location
  					return null;
  				} else if (e === "done") {
  					// valid hover...continue
  				} else {
  					// a real exception
  					throw e;
  				}
  			}
  			if (!toLookFor) {
  				// no hover target found
  				return null;
  			}
  			// must defer inferring the containing function block until the end
  			environment.defer = funcList.pop();
  			if (environment.defer && toLookFor === environment.defer.id) {
  				// don't defer if target is name of function
  				delete environment.defer;
  			}
  
  			if (environment.defer) {
  				// remove these comments from consideration until we are inferring the deferred
  				environment.deferredComments = extractDocComments(environment.comments, environment.defer.range);
  			}
  
  			var target = this._doVisit(root, environment);
  			var lookupName = toLookFor.type === "Identifier" ? toLookFor.name : 'this';
  			var maybeType = environment.lookupName(lookupName, toLookFor.extras.target || target, false, true);
  			if (maybeType) {
  				var hover = mTypes.styleAsProperty(lookupName, findName) + " : " + mTypes.createReadableType(maybeType.typeName, environment, true, 0, findName);
  				maybeType.hoverText = hover;
  				return maybeType;
  			} else {
  				return null;
  			}
  
  		},
  		/**
  		 * Computes the hover information for the provided offset
  		 */
  		computeHover: function(buffer, offset) {
  			return this._internalFindDefinition(buffer, offset, true);
  		},
  
  		findDefinition : function(buffer, offset) {
  			return this._internalFindDefinition(buffer, offset, false);
  		},
  
  		/**
  		 * Computes a summary of the file that is suitable to be stored locally and used as a dependency
  		 * in another file
  		 * @param {String} buffer
  		 * @param {String} fileName
  		 */
  		computeSummary: function(buffer, fileName) {
  			var root = mVisitor.parse(buffer);
  			if (!root) {
  				// assume a bad parse
  				return null;
  			}
  			var environment = createEnvironment({ buffer: buffer, uid : fileName, globalObjName : findGlobalObject(root.comments, this.lintOptions), comments : root.comments, indexer : this.indexer });
  			try {
  				this._doVisit(root, environment);
  			} catch (e) {
  				if (typeof scriptedLogger !== "undefined") {
  					scriptedLogger.error("Problem with: " + fileName, "CONTENT_ASSIST");
  					scriptedLogger.error(e.message, "CONTENT_ASSIST");
  					scriptedLogger.error(e.stack, "CONTENT_ASSIST");
  				}
  				throw (e);
  			}
  			var provided;
  			var kind;
  			var modType;
  			if (environment.amdModule) {
  				// provide the exports of the AMD module
  				// the exports is the return value of the final argument
  				var args = environment.amdModule["arguments"];
  				if (args && args.length > 0) {
  					modType = mTypes.extractReturnType(args[args.length-1].extras.inferredType);
  				} else {
  					modType = "Object";
  				}
  				kind = "AMD";
  			} else if (environment.commonjsModule) {
  				// a wrapped commonjs module
  				// we have already checked the correctness of this function
  				var exportsParam = environment.commonjsModule["arguments"][0].params[1];
  				modType = exportsParam.extras.inferredType;
  				provided = provided = environment.findType(modType);
  
  			} else {
  				// assume a non-module
  				provided = environment.globalScope();
  
  				if (provided.exports) {
  					// actually, commonjs
  					kind = "commonjs";
  					modType = provided.exports.typeName;
  				} else {
  					kind = "global";
  					modType = environment.globalTypeName();
  				}
  			}
  
  			// simplify the exported type
  			if (mTypes.isFunctionOrConstructor(modType) || environment.findType(modType).$$isBuiltin) {
  				// this module provides a built in type or a function
  				provided = modType;
  			} else {
  				// this module provides a composite type
  				provided = environment.findType(modType);
  			}
  
  
  			// now filter the builtins since they are always available
  			filterTypes(environment, kind, modType);
  
  			var allTypes = environment.getAllTypes();
  
  			return {
  				provided : provided,
  				types : allTypes,
  				kind : kind
  			};
  		}
  	};
  	return {
  		EsprimaJavaScriptContentAssistProvider : EsprimaJavaScriptContentAssistProvider
  	};
  });
  