summaryrefslogtreecommitdiffstatsabout
diff options
context:
space:
mode:
authorMike Rennie2013-11-29 15:16:54 (EST)
committer Gerrit Code Review @ Eclipse.org2013-12-02 10:54:46 (EST)
commit384831a5866bfe7ecc188a61e3d71f4d232c5556 (patch)
tree829d7c9f2444b4ec352494e9267a2819b029c35c
parent870e0135115e1986fca11cb159a6baca0b9c90b4 (diff)
downloadorg.eclipse.orion.client-384831a5866bfe7ecc188a61e3d71f4d232c5556.zip
org.eclipse.orion.client-384831a5866bfe7ecc188a61e3d71f4d232c5556.tar.gz
org.eclipse.orion.client-384831a5866bfe7ecc188a61e3d71f4d232c5556.tar.bz2
Bug 422520 - Re-write mark occurrences support to use estraverserefs/changes/48/19148/2
Change-Id: I9c21c6a73c787342f1bf5eff1a911b3a442fca22 Signed-off-by: Mike Rennie <Michael_Rennie@ca.ibm.com>
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/javascript/occurrences.js594
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/javascriptPlugin.js2
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/javascript/wordfinder.js64
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.html36
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.js33
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.html35
-rw-r--r--bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.js116
7 files changed, 503 insertions, 377 deletions
diff --git a/bundles/org.eclipse.orion.client.javascript/web/javascript/occurrences.js b/bundles/org.eclipse.orion.client.javascript/web/javascript/occurrences.js
index 36f9815..8550ace 100644
--- a/bundles/org.eclipse.orion.client.javascript/web/javascript/occurrences.js
+++ b/bundles/org.eclipse.orion.client.javascript/web/javascript/occurrences.js
@@ -11,415 +11,248 @@
*******************************************************************************/
/*global define console escope*/
define([
-"orion/Deferred"
-], function(Deferred) {
+'orion/Deferred',
+'orion/objects',
+'estraverse',
+'javascript/wordfinder'
+], function(Deferred, Objects, Estraverse, WordFinder) {
/**
- * @name javascript.JavaScriptOccurrences
- * @description creates a new instance of the outliner
- * @constructor
- * @public
+ * @name javascript.Visitor
+ * @description The AST visitor passed into estraverse
+ * @constrcutor
+ * @private
+ * @since 5.0
*/
- function JavaScriptOccurrences() {
+ function Visitor() {
}
- JavaScriptOccurrences.prototype = /** @lends javascript.JavaScriptOccurrences.prototype*/ {
- /**
- * @name isOccurrenceInSelScope
- * @description Computes if the occurrence scope is covered by the other scope
- * @function
- * @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} oScope The current occurrence scope
- * @param {Object} wScope The scope to check
- * @returns {Boolean} <code>true</code> if the given scope encloses the occurrence scope, <code>false</code> otherwise
- */
- isOccurrenceInSelScope: function(oScope, wScope) {
- if (oScope.global && wScope.global) {
- return true;
- }
- if (!oScope.global && !wScope.global && (oScope.name === wScope.name) && (oScope.loc.start.line === wScope.loc.start.line) &&
- (oScope.loc.start.column === wScope.loc.start.column)) {
- return true;
- }
- return false;
- },
+ Objects.mixin(Visitor.prototype, /** @lends javascript.Visitor.prototype */ {
+ occurrences: [],
+ defscope: null,
+ defnode: null,
+ scopes: [],
+ GENERAL: 1,
+ FUNCTION: 2,
/**
- * @name filterOccurrences
- * @description Filters the computed occurrences given the context
+ * @name enter
+ * @description Callback from estraverse when a node is starting to be visited
* @function
* @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} context The current context
- * @returns {Array|null} Returns the array of matches or <code>null</code>
+ * @memberof javascript.Visitor.prototype
+ * @param {Object} node The AST node currently being visited
+ * @returns The status if we should continue visiting
*/
- filterOccurrences: function(context) {
- if (!context.mScope) {
- return null;
- }
- var matches = [];
- for (var i = 0; i < context.occurrences.length; i++) {
- if (this.isOccurrenceInSelScope(context.occurrences[i].scope, context.mScope)) {
- matches.push({
- readAccess: context.occurrences[i].readAccess,
- line: context.occurrences[i].node.loc.start.line,
- start: context.occurrences[i].node.loc.start.column + 1,
- end: context.occurrences[i].node.loc.end.column,
- description: (context.occurrences[i].readAccess ? 'Occurrence of "' : 'Write occurrence of "') + context.word + '"' //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
- });
- }
- }
- return matches;
- },
-
- /**
- * @name updateScope
- * @description Update the given scope when leaving the given node. This function removes scope elements
- * when done processing <code>FunctionDeclaration</code> and <code>FunctionExpression</code> nodes.
- * @function
- * @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} node The AST node we are currently inspecting
- * @param {Object} scope The current scope we are in
- */
- updateScope: function(node, scope) {
- if (!node || !node.type || !scope) {
- return;
- }
- switch (node.type) {
- case 'FunctionDeclaration': //$NON-NLS-0$
- scope.pop();
+ enter: function(node) {
+ switch(node.type) {
+ case 'Program':
+ this.occurrences = [];
+ this.defscope = null;
+ this.defnode = null;
+
+ this.scopes.push({range: node.range});
break;
- case 'FunctionExpression': //$NON-NLS-0$
- scope.pop();
- break;
- }
- },
-
- /**
- * @name traverse
- * @description Walks the given node and context to find occurrences, returns if the traversal
- * should continue to child nodes
- * @function
- * @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} node the AST node we are currently visiting
- * @param {Object} context the current context
- * @param {Function} func A function to call on the node and context to process the node in the given context
- * @returns {Boolean} <code>true</code> if we should continue traversing the given node and its children, <code>false</code> otherwise
- */
- traverse: function(node, context, func) {
- if (func(node, context)) {
- return false; // stop traversal
- }
- for (var key in node) {
- if (node.hasOwnProperty(key)) {
- var child = node[key];
- if (child && typeof child === 'object' && child !== null) { //$NON-NLS-0$
- if (Array.isArray(child)) {
- for (var i=0; i<child.length; i++) {
- if (!this.traverse(child[i], context, func)) {
- return false;
- }
+ case 'FunctionDeclaration':
+ this.checkId(node.id, this.FUNCTION, true);
+ //we want the parent scope for a declaration, otherwise we leave it right away
+ this.scopes.push({range: node.range});
+ if (node.params) {
+ for (var i = 0; i < node.params.length; i++) {
+ if(this.checkId(node.params[i], this.GENERAL, true)) {
+ return Estraverse.VisitorOption.Skip;
}
- } else {
- if (!this.traverse(child, context, func)) {
- return false;
+ }
+ }
+ break;
+ case 'FunctionExpression':
+ if (node.params) {
+ this.scopes.push({range: node.range});
+ for (var j = 0; j < node.params.length; j++) {
+ if(this.checkId(node.params[j], this.GENERAL, true)) {
+ return Estraverse.VisitorOption.Skip;
}
}
}
- }
+ break;
+ case 'AssignmentExpression':
+ var leftNode = node.left;
+ this.checkId(leftNode);
+ if (leftNode.type === 'MemberExpression') { //$NON-NLS-0$
+ this.checkId(leftNode.object);
+ }
+ this.checkId(node.right);
+ break;
+ case 'ArrayExpression':
+ if (node.elements) {
+ for (var k = 0; k < node.elements.length; k++) {
+ this.checkId(node.elements[k]);
+ }
+ }
+ break;
+ case 'MemberExpression':
+ this.checkId(node.object);
+ if (node.computed) { //computed = true for [], false for . notation
+ this.checkId(node.property);
+ }
+ break;
+ case 'BinaryExpression':
+ this.checkId(node.left);
+ this.checkId(node.right);
+ break;
+ case 'UnaryExpression':
+ this.checkId(node.argument);
+ break;
+ case 'IfStatement':
+ this.checkId(node.test);
+ break;
+ case 'SwitchStatement':
+ this.checkId(node.discriminant);
+ break;
+ case 'UpdateExpression':
+ this.checkId(node.argument);
+ break;
+ case 'ConditionalExpression':
+ this.checkId(node.test);
+ this.checkId(node.consequent);
+ this.checkId(node.alternate);
+ break;
+ case 'CallExpression':
+ this.checkId(node.callee, this.FUNCTION, false);
+ if (node.arguments) {
+ for (var l = 0; l < node.arguments.length; l++) {
+ this.checkId(node.arguments[l]);
+ }
+ }
+ break;
+ case 'ReturnStatement':
+ this.checkId(node.argument);
+ break;
+ case 'ObjectExpression':
+ if(node.properties) {
+ var len = node.properties.length;
+ for (var m = 0; m < len; m++) {
+ this.checkId(node.properties[m].value);
+ }
+ }
+ break;
+ case 'VariableDeclarator': //$NON-NLS-0$
+ this.checkId(node.id, this.GENERAL, true);
+ break;
+ case 'NewExpression':
+ this.checkId(node.callee, this.FUNCTION, false);
+ break;
}
- this.updateScope(node, context.scope);
- return true;
},
/**
- * @name checkIdentifier
- * @description Checks if the given identifier matches the occurrence we are looking for
- * @function
- * @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} node The AST node we are inspecting
- * @param {Object} context the current occurrence context
- * @returns {Boolean} <code>true</code> if we should continue traversing the given node and its children, <code>false</code> otherwise
- */
- checkIdentifier: function(node, context) {
- if (node && node.type === 'Identifier') { //$NON-NLS-0$
- if (node.name === context.word) {
- return true;
- }
- }
- return false;
- },
-
- /**
- * @name findMatchingDeclaration
- * @description Finds the first scope in the array that has a declaration
+ * @name leave
+ * @description Callback from estraverse when visitation of a node has completed
* @function
* @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Array} scope the array of scopes
- * @returns {Object} Returns the scope with the declaration or <code>null</code>
+ * @memberof javascript.Visitor.prototype
+ * @param {Object} node The AST node that ended its visitation
*/
- findMatchingDeclarationScope: function(scope) {
- for (var i = scope.length - 1; i >= 0; i--) {
- if (scope[i].decl) {
- return scope[i];
+ leave: function(node) {
+ if(node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
+ //if we leave the defining scope
+ var scope = this.scopes.pop();
+ if(this.defscope) {
+ if(this.defscope.range[0] === scope.range[0] && this.defscope.range[1] === scope.range[1]) {
+ //we just popped out of the scope the word was defined in, we can quit
+ return Estraverse.VisitorOption.Break;
+ }
}
}
- return null;
},
-
+
/**
- * @name addOccurrence
- * @description Adds an occurrence for the given node and context to the collection
+ * @name setContext
+ * @description Sets the current context for the visitor to match for occurrences
* @function
* @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} node The AST we found the occurrence in
- * @param {Object} context The occurrence context
- * @param {Boolean} readAccess if there is read access to the given occurrence
+ * @memberof javascript.Visitor.prototype
+ * @param {Object} ctxt The context to match for occurrences
*/
- addOccurrence: function(node, context, readAccess) {
- if (node) {
- if (readAccess === undefined) {
- readAccess = true;
- }
-
- var mScope = this.findMatchingDeclarationScope(context.scope);
- if (!mScope) {
- return;
- }
-
- if ((node.range[0] <= context.start) && (context.end <= node.range[1])) {
- if (mScope) {
- context.mScope = mScope;
- } else {
- console.error("matching declaration scope for selected type not found " + context.word); //$NON-NLS-0$
- }
- }
-
- context.occurrences.push({
- readAccess: readAccess,
- node: node,
- scope: mScope
- });
- }
+ setContext: function(ctxt) {
+ this.context = ctxt;
},
-
+
/**
- * @name findOccurrence
- * @description Finds the occurrence from the given context in the given AST node
+ * @name checkId
+ * @description Checks if the given identifier matches the occurrence we are looking for
* @function
* @private
* @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} node The current AST node to check
- * @param {Object} context The occurrence context
+ * @param {Object} node The AST node we are inspecting
+ * @param {Number} kind The kind of occurrence to consider
+ * @param {Boolean} candefine If the given node can define the word we are looking for
+ * @returns {Boolean} <code>true</code> if we should skip the next nodes, <code>false</code> otherwise
*/
- findOccurrence: function(node, context) {
- if (!node || !node.type) {
- return;
- }
- var varScope, curScope, i;
- switch (node.type) {
- case 'Program': //$NON-NLS-0$
- curScope = {
- global: true,
- name: null,
- decl: false
- };
- context.scope.push(curScope);
- break;
- case 'VariableDeclarator': //$NON-NLS-0$
- if (this.checkIdentifier(node.id, context)) {
- varScope = context.scope.pop();
- varScope.decl = true;
- context.scope.push(varScope);
- this.addOccurrence(node.id, context, false);
- }
- if (node.init) {
- if (this.checkIdentifier(node.init, context)) {
- this.addOccurrence(node.init, context);
- break;
- }
- if (node.init.type === 'ObjectExpression') { //$NON-NLS-0$
- var properties = node.init.properties;
- for (i = 0; i < properties.length; i++) {
- //if (checkIdentifier (properties[i].key, context)) {
- // var varScope = scope.pop();
- // varScope.decl = true;
- // scope.push(varScope);
- // addOccurrence (scope, properties[i].key, context, occurrences, false);
- //}
- if (this.checkIdentifier(properties[i].value, context)) {
- this.addOccurrence(properties[i].value, context);
- }
+ checkId: function(node, kind, candefine) {
+ if (node && node.type === 'Identifier') { //$NON-NLS-0$
+ if (node.name === this.context.word) {
+ if(candefine) {
+ if(this.defscope && this.defnode) {
+ //trying to re-define, we can break since any matches past here would not be the original definition
+ return true;
}
- }
- }
- break;
- case 'ArrayExpression': //$NON-NLS-0$
- if (node.elements) {
- for (i = 0; i < node.elements.length; i++) {
- if (this.checkIdentifier(node.elements[i], context)) {
- this.addOccurrence(node.elements[i], context);
+ var len = this.scopes.length;
+ var scope = len > 0 ? this.scopes[len-1] : null;
+ //does the scope enclose it?
+ if(scope && (scope.range[0] <= this.context.start) && (scope.range[1] >= this.context.end)) {
+ this.defscope = scope;
}
- }
- }
- break;
- case 'AssignmentExpression': //$NON-NLS-0$
- var leftNode = node.left;
- if (this.checkIdentifier(leftNode, context)) {
- this.addOccurrence(leftNode, context, false);
- }
- if (leftNode.type === 'MemberExpression') { //$NON-NLS-0$
- if (this.checkIdentifier(leftNode.object, context)) {
- this.addOccurrence(leftNode.object, context, false);
- }
- }
- var rightNode = node.right;
- if (this.checkIdentifier(rightNode, context)) {
- this.addOccurrence(rightNode, context);
- }
- break;
- case 'MemberExpression': //$NON-NLS-0$
- if (this.checkIdentifier(node.object, context)) {
- this.addOccurrence(node.object, context);
- }
- if (node.computed) { //computed = true for [], false for . notation
- if (this.checkIdentifier(node.property, context)) {
- this.addOccurrence(node.property, context);
- }
- }
- break;
- case 'BinaryExpression': //$NON-NLS-0$
- if (this.checkIdentifier(node.left, context)) {
- this.addOccurrence(node.left, context);
- }
- if (this.checkIdentifier(node.right, context)) {
- this.addOccurrence(node.right, context);
- }
- break;
- case 'UnaryExpression': //$NON-NLS-0$
- if (this.checkIdentifier(node.argument, context)) {
- this.addOccurrence(node.argument, context, node.operator === 'delete' ? false : true); //$NON-NLS-0$
- }
- break;
- case 'IfStatement': //$NON-NLS-0$
- if (this.checkIdentifier(node.test, context)) {
- this.addOccurrence(node.test, context);
- }
- break;
- case 'SwitchStatement': //$NON-NLS-0$
- if (this.checkIdentifier(node.discriminant, context)) {
- this.addOccurrence(node.discriminant, context, false);
- }
- break;
- case 'UpdateExpression': //$NON-NLS-0$
- if (this.checkIdentifier(node.argument, context)) {
- this.addOccurrence(node.argument, context, false);
- }
- break;
- case 'ConditionalExpression': //$NON-NLS-0$
- if (this.checkIdentifier(node.test, context)) {
- this.addOccurrence(node.test, context);
- }
- if (this.checkIdentifier(node.consequent, context)) {
- this.addOccurrence(node.consequent, context);
- }
- if (this.checkIdentifier(node.alternate, context)) {
- this.addOccurrence(node.alternate, context);
- }
- break;
- case 'FunctionDeclaration': //$NON-NLS-0$
- curScope = {
- global: false,
- name: node.id.name,
- loc: node.loc,
- decl: false
- };
- context.scope.push(curScope);
- if (node.params) {
- for (i = 0; i < node.params.length; i++) {
- if (this.checkIdentifier(node.params[i], context)) {
- varScope = context.scope.pop();
- varScope.decl = true;
- context.scope.push(varScope);
- this.addOccurrence(node.params[i], context, false);
+ if(node.range[0] <= this.context.start) {
+ this.defnode = node.range;
+ this.defnode.kind = !kind ? this.GENERAL : kind;
}
}
- }
- break;
- case 'FunctionExpression': //$NON-NLS-0$
- curScope = {
- global: false,
- name: null,
- loc: node.loc,
- decl: false
- };
- context.scope.push(curScope);
- if (!node.params) {
- break;
- }
- for (i = 0; i < node.params.length; i++) {
- if (this.checkIdentifier(node.params[i], context)) {
- varScope = context.scope.pop();
- varScope.decl = true;
- context.scope.push(varScope);
- this.addOccurrence(node.params[i], context, false);
- }
- }
- break;
- case 'CallExpression': //$NON-NLS-0$
- if (!node.arguments) {
- break;
- }
- for (var j = 0; j < node.arguments.length; j++) {
- if (this.checkIdentifier(node.arguments[j], context)) {
- this.addOccurrence(node.arguments[j], context);
+ if(this.defscope && this.defnode && this.defnode.kind === (!kind ? this.GENERAL : kind)) {
+ this.occurrences.push({
+ start: node.range[0],
+ end: node.range[1]
+ });
}
}
- break;
- case 'ReturnStatement': //$NON-NLS-0$
- if (this.checkIdentifier(node.argument, context)) {
- this.addOccurrence(node.argument, context);
- }
}
- },
-
+ return false;
+ }
+ });
+
+ Visitor.prototype.constructor = Visitor;
+
+ /**
+ * @name javascript.JavaScriptOccurrences
+ * @description creates a new instance of the outliner
+ * @constructor
+ * @public
+ */
+ function JavaScriptOccurrences() {
+ }
+
+ Objects.mixin(JavaScriptOccurrences.prototype, /** @lends javascript.JavaScriptOccurrences.prototype*/ {
+
+ visitor: null,
+
/**
- * @name getOccurrences
- * @description Computes the occurrences from the given AST and context
+ * @name getVisitor
+ * @description Delegate function to get the visitor
* @function
* @private
- * @memberof javascript.JavaScriptOccurrences.prototype
- * @param {Object} ast the AST to find occurrences in
- * @param {Object} context the current occurrence context
- * @returns {Array|null} Returns the found array of occurrences or <code>null</code>
+ * @memberof javascript.JSOutliner.prototype
+ * @param {Object} context The context (item) to find occurrrences for
+ * @returns The instance of {Visitor} to use
*/
- getOccurrences: function(ast, context) {
- if (ast) {
- this.traverse(ast, context, function(node, context) {
- var found = false;
- if (node.range && node.name && (node.range[0] <= context.start) && (context.end <= node.range[1])) {
- context.word = node.name;
- found = true;
- }
- return found;
- });
-
- if (!context || !context.word) {
- return null;
- }
- context.scope = [];
- context.occurrences = [];
- this.traverse(ast, context, this.findOccurrence.bind(this));
- return this.filterOccurrences(context);
- }
- console.error("AST is null"); //$NON-NLS-0$
- return null;
+ getVisitor: function(context) {
+ if(!this.visitor) {
+ this.visitor = new Visitor();
+ this.visitor.enter = this.visitor.enter.bind(this.visitor);
+ this.visitor.leave = this.visitor.leave.bind(this.visitor);
+ this.visitor.setContext = this.visitor.setContext.bind(this.visitor);
+ }
+ this.visitor.setContext(context);
+ return this.visitor;
},
/**
@@ -429,23 +262,32 @@ define([
* @public
* @memberof javascript.JavaScriptOccurrences.prototype
* @param {Object} editorContext The current editor context
- * @param {Object} ctx The current selection context
+ * @param {Object} ctxt The current selection context
*/
- computeOccurrences: function(editorContext, ctx) {
- var d = new Deferred();
+ computeOccurrences: function(editorContext, ctxt) {
var that = this;
- editorContext.getAST().then(function(ast) {
- var context = {
- start: ctx.selection.start,
- end: ctx.selection.end,
- mScope: null
- };
- d.resolve(that.getOccurrences(ast, context));
+ var word;
+ return editorContext.getText().then(function(text) {
+ word = WordFinder.findWord(text, ctxt.selection.start);
+ if(word) {
+ return editorContext.getAST();
+ }
+ }).then(function(ast) {
+ if(ast) {
+ var context = {
+ start: ctxt.selection.start,
+ end: ctxt.selection.end,
+ word: word,
+ mScope: null
+ };
+ var visitor = that.getVisitor(context);
+ Estraverse.traverse(ast, visitor);
+ return visitor.occurrences;
+ }
+ return [];
});
- return d;
}
-
- };
+ });
JavaScriptOccurrences.prototype.contructor = JavaScriptOccurrences;
diff --git a/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/javascriptPlugin.js b/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/javascriptPlugin.js
index 4acd534..b47f074 100644
--- a/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/javascriptPlugin.js
+++ b/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/javascriptPlugin.js
@@ -91,7 +91,7 @@ define([
provider.registerServiceProvider("orion.edit.contentassist", new EsprimaAssist.EsprimaJavaScriptContentAssistProvider(),
{
contentType: ["application/javascript"],
- name: "Esprima based JavaScript content assist",
+ name: "JavaScript content assist",
id: "orion.edit.contentassist.esprima"
});
diff --git a/bundles/org.eclipse.orion.client.javascript/web/javascript/wordfinder.js b/bundles/org.eclipse.orion.client.javascript/web/javascript/wordfinder.js
new file mode 100644
index 0000000..edd5623
--- /dev/null
+++ b/bundles/org.eclipse.orion.client.javascript/web/javascript/wordfinder.js
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * @license
+ * Copyright (c) 2013 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+/*global define*/
+define([
+], function() {
+
+ var WordFinder = {
+
+ punc: '\n\t\r (){}[]:;,.+=-*^&@!%~`\'\"\/\\',
+
+ /**
+ * @name findWord
+ * @description Finds the word from the start position
+ * @function
+ * @public
+ * @memberof javascript.Wordfinder
+ * @param {String} text The text of the source to find the word in
+ * @param {Number} start The current start position of the carat
+ * @returns {String} Returns the computed word from the given string and offset or <code>null</code>
+ */
+ findWord: function(text, start) {
+ if(text && start) {
+ var ispunc = this.punc.indexOf(text.charAt(start)) > -1;
+ var pos = ispunc ? start-1 : start;
+ while(pos >= 0) {
+ if(this.punc.indexOf(text.charAt(pos)) > -1) {
+ break;
+ }
+ pos--;
+ }
+ var s = pos;
+ pos = start;
+ while(pos <= text.length) {
+ if(this.punc.indexOf(text.charAt(pos)) > -1) {
+ break;
+ }
+ pos++;
+ }
+ if((s === start || (ispunc && (s === start-1))) && pos === start) {
+ return null;
+ }
+ else if(s === start) {
+ return text.substring(s, pos);
+ }
+ else {
+ return text.substring(s+1, pos);
+ }
+ }
+ return null;
+ }
+ };
+
+ return WordFinder;
+});
+ \ No newline at end of file
diff --git a/bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.html b/bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.html
new file mode 100644
index 0000000..af337dc
--- /dev/null
+++ b/bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="../../requirejs/require.js"></script>
+ <script>
+ /*global require window*/
+ require({
+ baseUrl: '../../',
+ paths: {
+ text: 'requirejs/text',
+ i18n: 'requirejs/i18n',
+ domReady: 'requirejs/domReady',
+ estraverse: 'estraverse/estraverse'
+ },
+ isTest: true
+ });
+
+ window.onload = function() {
+ require(["orion/test","javascript/jsOutlinerTests.js"], function(test, testcase) {
+ test.run(testcase);
+ });
+ };
+ </script>
+</head>
+<body>
+<h2>JavaScript Source Outline Tests</h2>
+<p>
+This test suite provides the following tests:
+<ul>
+<li><strong></strong> - </li>
+<li><strong></strong> - </li>
+<li><strong></strong> - </li>
+</ul>
+</p>
+</body>
+</html> \ No newline at end of file
diff --git a/bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.js b/bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.js
new file mode 100644
index 0000000..8da32de
--- /dev/null
+++ b/bundles/org.eclipse.orion.client.javascript/web/js-tests/jsOutlinerTests.js
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * @license
+ * Copyright (c) 2013 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+/*global console:true define*/
+define([
+ "orion/assert"
+], function(assert) {
+
+ var Tests = {
+
+ test_funcDeclaration1: function() {
+
+ },
+
+ test_funcExpression1: function() {
+
+ },
+
+ test_objExpression1: function() {
+
+ }
+
+ };
+
+ return Tests;
+}); \ No newline at end of file
diff --git a/bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.html b/bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.html
new file mode 100644
index 0000000..cd35128
--- /dev/null
+++ b/bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="../../requirejs/require.js"></script>
+ <script>
+ /*global require window*/
+ require({
+ baseUrl: '../../',
+ paths: {
+ text: 'requirejs/text',
+ i18n: 'requirejs/i18n',
+ domReady: 'requirejs/domReady'
+ },
+ isTest: true
+ });
+
+ window.onload = function() {
+ require(["orion/test","js-tests/wordFinderTests.js"], function(test, testcase) {
+ test.run(testcase);
+ });
+ };
+ </script>
+</head>
+<body>
+<h2>JavaScript Word Finder Tests</h2>
+<p>
+This suite tests finding words in JavaScript source.
+<br><br>
+The WordFinder class is currently used by:
+<ul>
+<li>Mark Occurrences</li>
+</ul>
+</p>
+</body>
+</html> \ No newline at end of file
diff --git a/bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.js b/bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.js
new file mode 100644
index 0000000..cc26296
--- /dev/null
+++ b/bundles/org.eclipse.orion.client.javascript/web/js-tests/wordFinderTests.js
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * @license
+ * Copyright (c) 2013 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+/*global console:true define*/
+define([
+ 'orion/assert',
+ 'javascript/wordfinder'
+], function(Assert, WordFinder) {
+
+ var Tests = {
+
+ test_findWord1: function() {
+ var word = WordFinder.findWord('function(param1, param2)', 12);
+ Assert.equal(word, 'param1', 'Should have found the word param1');
+ },
+
+ test_findWord2: function() {
+ var word = WordFinder.findWord('function(param1, param2)', 9);
+ Assert.equal(word, 'param1', 'Should have found the word param1');
+ },
+
+ test_findWord3: function() {
+ var word = WordFinder.findWord('function(param1, param2)', 17);
+ Assert.equal(word, 'param2', 'Should have found the word param2');
+ },
+
+ test_findWord4: function() {
+ var word = WordFinder.findWord('var foo.bar = function(param1, param2)', 4);
+ Assert.equal(word, 'foo', 'Should have found the word foo');
+ },
+
+ test_findWord5: function() {
+ var word = WordFinder.findWord('var foo.bar = function(param1, param2)', 8);
+ Assert.equal(word, 'bar', 'Should have found the word bar');
+ },
+
+ test_findWord6: function() {
+ var word = WordFinder.findWord('f =function(p1) {', 3);
+ Assert.equal(word, 'function', 'Should have found word function');
+ },
+
+ test_findWord7: function() {
+ var word = WordFinder.findWord('f ={foo:true', 4);
+ Assert.equal(word, 'foo', 'Should have found word foo');
+ },
+
+ test_findWord8: function() {
+ var word = WordFinder.findWord('function(param1, param2)', 15);
+ Assert.equal(word, 'param1', 'Should have found word param1');
+ },
+
+ test_findWord9: function() {
+ var word = WordFinder.findWord('var foo.bar = function(param1, param2)', 7);
+ Assert.equal(word, 'foo', 'Should have found word foo');
+ },
+
+ test_findWord10: function() {
+ var word = WordFinder.findWord(' foo.bar = function(param1, param2)', 4);
+ Assert.equal(word, 'foo', 'Should have found word foo');
+ },
+
+ test_findWord11: function() {
+ var word = WordFinder.findWord(' foo.bar = function(param1, param2)', 2);
+ Assert.equal(word, 'foo', 'Should have found word foo');
+ },
+
+ test_findNoWord1: function() {
+ var word = WordFinder.findWord('f: function(p1, p2)', 2);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord2: function() {
+ var word = WordFinder.findWord('f: function(p1, p2)', 15);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord3: function() {
+ var word = WordFinder.findWord('f: function(p1) {', 16);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord4: function() {
+ var word = WordFinder.findWord('f: function(p1) {', 17);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord5: function() {
+ var word = WordFinder.findWord('f = function(p1) {', 2);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord6: function() {
+ var word = WordFinder.findWord('f = function(p1) {', 3);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord7: function() {
+ var word = WordFinder.findWord('var a = [1, 2]', 7);
+ Assert.equal(word, null, 'Should have found no word');
+ },
+
+ test_findNoWord8: function() {
+ var word = WordFinder.findWord('var a = [1, 2]', 14);
+ Assert.equal(word, null, 'Should have found no word');
+ }
+ };
+
+ return Tests;
+}); \ No newline at end of file