ctags icon indicating copy to clipboard operation
ctags copied to clipboard

JavaScript: function/method named 'get' or 'set' causes various tag generation issues

Open the-noob-of-northrose opened this issue 3 years ago • 2 comments

Having a function/method named 'get' or 'set' seems to break the parser in a couple of situations (possibly/probably related to how the parser is trying to handle ES6 class getters/setters).

All of my example cases illustrate only with 'get', but the same symptoms occur if you replace 'get' with 'set'.


General Information

The name of the parser: jscript

The command line you used to run ctags:

$ ctags --options=NONE <filename>

The version of ctags (I realize I'm not up-to-date but a perusal of changes to the jscript parser between then and now suggested that this issue should still be present):

$ ctags --version
Universal Ctags 5.9.0, Copyright (C) 2015 Universal Ctags Team
Universal Ctags is derived from Exuberant Ctags.
Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert
  Compiled: Sep  3 2021, 18:12:18
  URL: https://ctags.io/
  Optional compiled features: +wildcards, +regex, +gnulib_regex, +iconv, +option-directory, +xpath, +json, +interactive, +sandbox, +yaml, +packcc, +optscript

How do you get ctags binary: Ubuntu 22.04 repository (via apt)


:x: Case 1: Not using ES6 class, "normal" function declaration of 'get'

The 'get' function gets no tag. Neither having 'get' accept arguments nor putting anything in its code block makes a difference.

The content of input file:

function definedWithFunctionBefore() {};
const functionExpressionBefore = () => {};
let variableBefore = "foo";
function get() {};
function definedWithFunctionAfter() {};
const functionExpressionAfter = () => {};
let variableAfter = "bar";

The tags output you are not satisfied with:

definedWithFunctionAfter        NonClass.js     /^function definedWithFunctionAfter() {};$/;"   f
definedWithFunctionBefore       NonClass.js     /^function definedWithFunctionBefore() {};$/;"  f
functionExpressionAfter NonClass.js     /^const functionExpressionAfter = () => {};$/;" C
functionExpressionBefore        NonClass.js     /^const functionExpressionBefore = () => {};$/;"        C
variableAfter   NonClass.js     /^let variableAfter = "bar";$/;"        v
variableBefore  NonClass.js     /^let variableBefore = "foo";$/;"       v

The tags output you expect: Previous output plus the following:

get    NonClass.js     /^function get() {};$/;"       f

✔️ Case 2: Not using ES6 class, 'get' as function expression

All tags generated as expected

Input file:

function definedWithFunctionBefore() {};
const functionExpressionBefore = () => {};
let variableBefore = "foo";
const get = function () {};
// also works as arrow function // const get = () => {};
function definedWithFunctionAfter() {};
const functionExpressionAfter = () => {};
let variableAfter = "bar";

Tags expected == tags actual:

definedWithFunctionAfter        NonClass.js     /^function definedWithFunctionAfter() {};$/;"   f
definedWithFunctionBefore       NonClass.js     /^function definedWithFunctionBefore() {};$/;"  f
functionExpressionAfter NonClass.js     /^const functionExpressionAfter = () => {};$/;" C
functionExpressionBefore        NonClass.js     /^const functionExpressionBefore = () => {};$/;"        C
get     NonClass.js     /^const get = function () {};$/;"       f
variableAfter   NonClass.js     /^let variableAfter = "bar";$/;"        v
variableBefore  NonClass.js     /^let variableBefore = "foo";$/;"       v

❌ Case 3: 'get' as a method in an ES6 class, no arguments

  • No tag for 'get'
  • No tag for any method or class field following 'get'

Input file:

class TestClass {
        methodBefore() {};
        fieldBefore = "foo";
        get() {};
        methodAfter() {};
        fieldAfter = "bar";
}

The tags output you are not satisfied with:

TestClass       TestClass.js    /^class TestClass {$/;" c
fieldBefore     TestClass.js    /^      fieldBefore = "foo";$/;"        M       class:TestClass
methodBefore    TestClass.js    /^      methodBefore() {};$/;"  m       class:TestClass

The tags output you expect: Previous output plus the following:

fieldAfter      TestClass.js    /^      fieldAfter = "bar";$/;" M       class:TestClass
get    TestClass.js    /^      get() {};$/;"  m       class:TestClass
methodAfter     TestClass.js    /^      methodAfter() {};$/;"   m       class:TestClass

❌ Case 4: 'get' as a method in an ES6 class, with argument

  • No tag for 'get'
  • No tag for any method or class field following 'get'
  • Both fieldBefore and bar getting field tags from the definition of method 'get' despite lack of tag for 'get' itself

Input file:

class TestClass {
        methodBefore() {};
        fieldBefore = "foo";
        get(bar) {return this.fieldBefore + bar};
        methodAfter() {};
        fieldAfter = "baz";
}

The tags output you are not satisfied with:

TestClass       TestClass.js    /^class TestClass {$/;" c
bar     TestClass.js    /^      get(bar) {return this.fieldBefore + bar};$/;"   M       class:TestClass
fieldBefore     TestClass.js    /^      fieldBefore = "foo";$/;"        M       class:TestClass
fieldBefore     TestClass.js    /^      get(bar) {return this.fieldBefore + bar};$/;"   M       class:TestClass
methodBefore    TestClass.js    /^      methodBefore() {};$/;"  m       class:TestClass

The tags output you expect:

TestClass       TestClass.js    /^class TestClass {$/;" c
fieldAfter      TestClass.js    /^      fieldAfter = "baz";$/;" M       class:TestClass
fieldBefore     TestClass.js    /^      fieldBefore = "foo";$/;"        M       class:TestClass
get    TestClass.js    /^      get(bar) {return this.fieldBefore + bar};$/;"  m       class:TestClass
methodAfter     TestClass.js    /^      methodAfter() {};$/;"   m       class:TestClass
methodBefore    TestClass.js    /^      methodBefore() {};$/;"  m       class:TestClass

✔️ Case 5: proper ES6 getter

All tags generated as expected

Input file:

class TestClass {
        methodBefore() {};
        fieldBefore = "foo";
        get es6Getter() {return "you got " + this.fieldBefore};
        methodAfter(bar) {return bar};
        fieldAfter = "baz";
}

Tags expected == tags actual:

TestClass       TestClass.js    /^class TestClass {$/;" c
es6Getter       TestClass.js    /^      get es6Getter() {return "you got " + this.fieldBefore};$/;"     G       class:TestClass
fieldAfter      TestClass.js    /^      fieldAfter = "baz";$/;" M       class:TestClass
fieldBefore     TestClass.js    /^      fieldBefore = "foo";$/;"        M       class:TestClass
methodAfter     TestClass.js    /^      methodAfter(bar) {return bar};$/;"      m       class:TestClass
methodBefore    TestClass.js    /^      methodBefore() {};$/;"  m       class:TestClass

the-noob-of-northrose avatar Feb 06 '23 18:02 the-noob-of-northrose

Quite a helpful bug report! Thank you.

masatake avatar Feb 07 '23 09:02 masatake

Cases 3 & 4 work with the latest version of ctags. I will debug case 1 when I can find some time.

jafl avatar Feb 09 '23 01:02 jafl

Case 1 fixed in https://github.com/universal-ctags/ctags/pull/3761

jafl avatar Jul 05 '23 00:07 jafl