diff --git a/bundles/org.eclipse.orion.client.javascript/web/javascript/finder.js b/bundles/org.eclipse.orion.client.javascript/web/javascript/finder.js index 1cac8a2..2207a1a 100644 --- a/bundles/org.eclipse.orion.client.javascript/web/javascript/finder.js +++ b/bundles/org.eclipse.orion.client.javascript/web/javascript/finder.js @@ -30,6 +30,7 @@ scopes: [], context: null, thisCheck: false, + objectPropCheck: false, /** * @name enter @@ -91,6 +92,8 @@ this.checkId(node.object); if (node.computed) { //computed = true for [], false for . notation this.checkId(node.property); + } else if (node.object.type === Estraverse.Syntax.ThisExpression){ + this.checkId(node.property, false, true); } break; case Estraverse.Syntax.BinaryExpression: @@ -135,6 +138,7 @@ //tag it prop.value.isprop = true; } + this.checkId(prop.key, true, true); this.checkId(prop.value); } } @@ -213,16 +217,27 @@ } break; } - } - else { - var kind = 'fe'; + } else if (this.objectPropCheck){ + switch(node.type) { + case Estraverse.Syntax.ObjectExpression: + this.scopes.push({range: node.range, occurrences: [], kind:'o'}); + if (this.defscope){ + return true; + } + break; + } + } else { + var kind; switch(node.type) { case Estraverse.Syntax.FunctionDeclaration: kind = 'fd'; - //$FALL-THROUGH$ - case Estraverse.Syntax.FunctionExpression: - this.scopes.push({range: node.range, occurrences: [], kind:kind}); break; + case Estraverse.Syntax.FunctionExpression: + kind = 'fe'; + break; + } + if (kind){ + this.scopes.push({range: node.range, occurrences: [], kind:kind}); } } return false; @@ -254,8 +269,16 @@ } break; } - } - else { + } else if (this.objectPropCheck) { + switch(node.type){ + case Estraverse.Syntax.ObjectExpression: + case Estraverse.Syntax.Program: + if(this._popScope()) { + return Estraverse.VisitorOption.Break; + } + break; + } + } else { switch(node.type) { case Estraverse.Syntax.FunctionExpression: case Estraverse.Syntax.FunctionDeclaration: @@ -277,8 +300,8 @@ */ _popScope: function() { var scope = this.scopes.pop(); - var len = scope.occurrences.length; var i; + var len = scope.occurrences.length; if(this.defscope) { for(i = 0; i < len; i++) { this.occurrences.push(scope.occurrences[i]); @@ -302,30 +325,32 @@ * @param {Boolean} candefine If the given node can define the word we are looking for * @returns {Boolean} true if we should skip the next nodes, false otherwise */ - checkId: function(node, candefine) { - if (!this.thisCheck && node && node.type === Estraverse.Syntax.Identifier) { - if (node.name === this.context.word) { - var scope = this.scopes[this.scopes.length-1]; // Always will have at least the program scope - if(candefine) { - if(this.defscope) { - // Re-defining, we want the last defining node previous to the selection, skip any future re-defines - if(node.range[0] > this.context.start) { - return true; - } else { - // Occurrences collected for the previous define are now invalid, fall through to mark this occurrence - this.occurrences = []; - scope.occurrences = []; + checkId: function(node, candefine, isObjectProp) { + if (!this.thisCheck && ((!isObjectProp && !this.objectPropCheck) || (isObjectProp && this.objectPropCheck))){ + if (node && node.type === Estraverse.Syntax.Identifier) { + if (node.name === this.context.word) { + var scope = this.scopes[this.scopes.length-1]; // Always will have at least the program scope + if(candefine) { + if(this.defscope) { + // Re-defining, we want the last defining node previous to the selection, skip any future re-defines + if(node.range[0] > this.context.start) { + return true; + } else { + // Occurrences collected for the previous define are now invalid, fall through to mark this occurrence + this.occurrences = []; + scope.occurrences = []; + } + } + //does the scope enclose it? + if(scope && (scope.range[0] <= this.context.start) && (scope.range[1] >= this.context.end)) { + this.defscope = scope; } } - //does the scope enclose it? - if(scope && (scope.range[0] <= this.context.start) && (scope.range[1] >= this.context.end)) { - this.defscope = scope; - } + scope.occurrences.push({ + start: node.range[0], + end: node.range[1] + }); } - scope.occurrences.push({ - start: node.range[0], - end: node.range[1] - }); } } return false; @@ -596,13 +621,13 @@ */ findOccurrences: function(ast, ctxt) { if(ast && ctxt) { - var token = this._getToken(ctxt.selection.start, ast); - if(!this._skip(token)) { + var node = this.findNode(ctxt.selection.start, ast, {parents: true}); + if(!this._skip(node)) { var context = { start: ctxt.selection.start, end: ctxt.selection.end, - word: this._nameFromNode(token), - token: token, + word: this._nameFromNode(node), + token: node, }; var visitor = this._getVisitor(context); Estraverse.traverse(ast, visitor); @@ -616,18 +641,18 @@ * @description If we should skip marking occurrences * @function * @private - * @param {Object} token The AST token + * @param {Object} node The AST node * @returns {Boolean} True if we shoud skip computing occurrences * @since 6.0 */ - _skip: function(token) { - if(!token) { + _skip: function(node) { + if(!node) { return true; } - if(token.type === 'Keyword') { //$NON-NLS-0$ - return token.value !== 'this'; //$NON-NLS-0$ + if(node.type === Estraverse.Syntax.ThisExpression) { + return false; } - return token.type !== Estraverse.Syntax.Identifier; + return node.type !== Estraverse.Syntax.Identifier; }, /** @@ -669,14 +694,14 @@ * @description Computes the node name to use while searching * @function * @private - * @param {Object} token The AST token + * @param {Object} node The AST token * @returns {String} The node name to use while seraching * @since 6.0 */ - _nameFromNode: function(token) { - switch(token.type) { - case Estraverse.Syntax.Identifier: return token.value; - case 'Keyword': return 'this'; //$NON-NLS-0$ //$NON-NLS-1$ + _nameFromNode: function(node) { + switch(node.type) { + case Estraverse.Syntax.Identifier: return node.name; + case Estraverse.Syntax.ThisExpression: return 'this'; //$NON-NLS-0$ } }, @@ -696,7 +721,19 @@ this.visitor.enter = this.visitor.enter.bind(this.visitor); this.visitor.leave = this.visitor.leave.bind(this.visitor); } - this.visitor.thisCheck = context.token && context.token.type === 'Keyword' && context.token.value === 'this'; //$NON-NLS-0$ //$NON-NLS-1$ + + // See if a 'this' keyword was selected + this.visitor.thisCheck = context.token && context.token.type === Estraverse.Syntax.ThisExpression; + + // See if an object property key is selected (or a usage of an object property such as this.prop()) + this.visitor.objectPropCheck = false; + var parent = context.token.parent ? context.token.parent : (context.token.parents && context.token.parents.length > 0 ? context.token.parents[context.token.parents.length-1] : null); + if (parent && parent.type === Estraverse.Syntax.Property){ + this.visitor.objectPropCheck = context.token === parent.key; + } else if (parent && (parent.type === Estraverse.Syntax.MemberExpression && parent.object && parent.object.type === Estraverse.Syntax.ThisExpression)){ + this.visitor.objectPropCheck = false; + } + this.visitor.context = context; return this.visitor; } diff --git a/bundles/org.eclipse.orion.client.javascript/web/js-tests/javascript/occurrencesTests.js b/bundles/org.eclipse.orion.client.javascript/web/js-tests/javascript/occurrencesTests.js index 51c178a..639c8ec 100644 --- a/bundles/org.eclipse.orion.client.javascript/web/js-tests/javascript/occurrencesTests.js +++ b/bundles/org.eclipse.orion.client.javascript/web/js-tests/javascript/occurrencesTests.js @@ -1517,6 +1517,7 @@ } }); }); + /** * Tests computing occurrences for do-while tests * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=435941 @@ -1712,5 +1713,72 @@ } }); }); + + /** + * Tests computing occurrences for object properties and references to them + * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=423083 + */ + it('test_object_properties_1A', function() { + editorContext.text = "Objects.mixin({ test1: function() {}, test2: function() { return this.test1(); } });"; + return occurrences.computeOccurrences(editorContext, setContext(19, 19)).then(function(results) { + try { + assertOccurrences(results, [{start:16, end:21}, {start:70, end:75}]); + } + finally { + tearDown(); + } + }); + }); + + /** + * Tests computing occurrences for object properties and references to them + * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=423083 + */ + it('test_object_properties_1B', function() { + editorContext.text = "Objects.mixin({ test1: function() {}, test2: function() { return this.test1(); } });"; + return occurrences.computeOccurrences(editorContext, setContext(71, 71)).then(function(results) { + try { + assertOccurrences(results, [{start:16, end:21}, {start:70, end:75}]); + } + finally { + tearDown(); + } + }); + }); + + /** + * Tests computing occurrences for object properties and references to them + * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=423083 + */ + it('test_object_properties_2A', function() { + editorContext.text = "var foo = { test1: function() {}, test2: function() { return this.test1(); } };"; + return occurrences.computeOccurrences(editorContext, setContext(15, 15)).then(function(results) { + try { + assertOccurrences(results, [{start:12, end:17}, {start:66, end:71}]); + } + finally { + tearDown(); + } + }); + }); + + /** + * Tests computing occurrences for object properties and references to them + * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=423083 + */ + it('test_object_properties_2B', function() { + editorContext.text = "var foo = { test1: function() {}, test2: function() { return this.test1(); } };"; + return occurrences.computeOccurrences(editorContext, setContext(66, 71)).then(function(results) { + try { + assertOccurrences(results, [{start:12, end:17}, {start:66, end:71}]); + } + finally { + tearDown(); + } + }); + }); + + + }); }); \ No newline at end of file