ctags icon indicating copy to clipboard operation
ctags copied to clipboard

JavaScript: SAPUI5 / OpenUI5 support

Open dfishburn opened this issue 8 years ago • 9 comments

The javascript parser supports SAP's UI5 framework, which was also open sourced as OpenUI5 (.

I added the initial support for it in Exuberant tags and I work with it daily. I have updated the support for it in universal tags. I will include a pull request for my changes. Fixed a few other issues along the way while I was testing.

I do have some questions about adding tests and what not. I see a bunch of directories under the Units directory. Could someone provide me some instruction on how to use these directories. First I would like to run all the javascript tests to make sure it is has no regressions.

I would also like to understand how to add my own set of tests for UI5.

Thank you. David

dfishburn avatar Dec 05 '16 19:12 dfishburn

Could you look at http://docs.ctags.io/en/latest/units.html ?

masatake avatar Dec 06 '16 00:12 masatake

After getting your pull request. I would like you to allow me to rearrange your code. I would like to introduce language named OpenUI5 based on your pull request.

Backgroupd:

Some of languages has "dialects". For example taken from your issue, Javascript language has OpenUI5 as a dialect. Another example is in #1227; C++ has CUDA as a dialect. Python has CPython. M4 has autoconf. Make has Automake....

I wonder how ctags deals dialects. The classical way is just extending kinds and file name patterns of the base language. #1220 is typical case. However, this approach has an issue. This approach consumes more kind letters. A namespace of kind letters are not small[a-zA-Z]. However, it is not enough large for adding many dialects. Not only us but also users will add dialects locally, soon we will see conflicts of kind letters.

Look at https://github.com/universal-ctags/ctags/issues/453 One wants to add 'c' (constant) kind to Ruby. However, it is used already for Rspec dialect.

From these experience I think dialect should has its own parser derived from the base language. The dialect has its own namespace. The namespace of the base language can be kept clean. Further more language: field can show the name of dialect.

How can we implement a dialect? I have tired to answer this question for long time. I have developed some facilities. I have to explain them.

masatake avatar Dec 06 '16 00:12 masatake

(http://openui5.org/getstarted.html)

masatake avatar Dec 06 '16 17:12 masatake

I misunderstood your question. To run test, do

$ make units

Only about javascript do

$ make units LANGUAGES=JavaScript

How to write a test case, please read http://docs.ctags.io/en/latest/units.html .

masatake avatar Dec 20 '16 16:12 masatake

After thinking I rename what I called "dialect" here to "subparser". See #1326, especially https://github.com/universal-ctags/ctags/commit/9ac9b7a0f666234dcb1eff6486f6b0fd93d6d299 . In this case, javascript parser acts as a base parser. openui5 parser can be implemented as a subparser based on the base parser.

masatake avatar Mar 26 '17 04:03 masatake

I convinced that UI5 code should be parsed by a subparser of JavaScript.

 sap.ui.controller("id.of.controller", {
    method_name1 : function... {
    },
    method_name2 : function ... {
    }
}

method_name1 and method_name2 are captured well with the JavaScript parser. However, to fill the scope fields of them, "id.of.controller" must be captured.

The question is what "kind" we should assign to "id.of.controller"? The kind should be part of UI5 subparser. The method_names may be tagged two times, one time by the JavaScript parser. the other time by UI5 parser. @dfishburn, do you have any comment?

@b4n, I'm working on introducing the corkAPI to the JavaScript parser to support "export and "import" modules to support Vue files.

masatake avatar Jun 13 '19 21:06 masatake

@b4n, I'm working on introducing the corkAPI to the JavaScript parser to support "export and "import" modules to support Vue files.

Not sure what that means, why do we need cork to parse import and export syntax? Using cork is nice anyway, but I'm not sure I understand why it's needed here, but I'm probably missing something.

Anyway that's nice :) Though, I'm afraid it won't be a trivial task, because the JS parser has its share of complex situations, and often emits parent tags after children (mostly to decide and type & co, so that can possibly be amended after initial emission). Good luck, and although I'm short on spare time lately (sorry :confused:) I'll try and look at anything you might need here (I know I have other PRs I need to look at, just give me a little time :slightly_smiling_face: ).

b4n avatar Jun 14 '19 07:06 b4n

and often emits parent tags after children

That's the point! Here is my solution.

commit 02a3804502981e2574dd0f64d47011d76390c3e7
Author: Masatake YAMATO <[email protected]>
Date:   Thu Jun 13 04:37:54 2019 +0900

    main: introduce a function putting a placeholder tag in the corkQueue
    
    Consider following input written in imaginary programming language X:
    
     A {
       B;
     }
    
    B have A as its parent scope.
    
    With using the cork API, you can make tags with the following step:
    
    1. make the tag for A, and get the cork index (index_A) for A, and
    2. make the tag for B filling scopeIndex field with index_A.
    
    In the most of all case, these steps work well.
    
    However, there is an exception.  The language X has following odd
    character; the kind of A can be decided after parsing B. For such
    language character, above steps don't work.
    
    You cannot make the tag for A before making B because the kind for A
    cannot be decided till parsing B. However, you cannot make the tag for
    B before making the tag for A because index_A is needed to make the
    tag for B. Mutual dependencies are there.
    
    This change introduces makePlaceholder function. This function extends
    the corkAPI to work well even if the mutual dependencies are there.
    
    makePlaceholder() allows to make a temporary tag (placeholder).
    Reconsider the language X. When the parser finds A, the parser can
    call makePlaceholder(A). The parser can make a tag for A without
    specifying its kind. It returns the corkIndex for the placeholder. The
    parser can use it index_A when parsing and making tag for B. The
    parser gets the cork index (index_B) for B.
    
    After parsing B, now the parser knows the real kind for A.  The parser
    can convert the placeholder tag to the real tag for A by patching the
    placeholder tag; the parser refills the kindIndex field of A with the
    real kind.
    
    Signed-off-by: Masatake YAMATO <[email protected]>

Newly introduced makePlaceholder() and its variant allows me to write following code:

+static void patchScopeOfEntries (int start, int end)
+{
+   if (! (end - start > 1))
+       return;
+
+   for (int i = start + 1; i < end; i++)
+   {
+       tagEntryInfo *e = getEntryInCorkQueue (i);
+       if (e && e->extensionFields.scopeIndex == start)
+           e->extensionFields.scopeIndex = end;
+   }
 }

...
@ -2190,11 +2222,14 @@ nextVar:
                        goto cleanUp;
                    }
 
-                   is_class = parseBlock (token, name->string);
+                   int p = makeSimplePlaceholder (name->string), q;
+                   is_class = parseBlock (token, p);
                    if ( is_class )
-                       makeClassTag (name, signature, NULL);
+                       q = makeClassTag (name, signature, NULL);
                    else
-                       makeFunctionTag (name, signature, is_generator);
+                       q = makeFunctionTag (name, signature, is_generator);
+                   patchScopeOfEntries (p, q);
+                   indexForName = q;
 
                    if ( vStringLength(secondary_name->string) > 0 )
                        makeFunctionTag (secondary_name, signature, is

masatake avatar Jun 14 '19 08:06 masatake

I'd like to show my current idea about modules. I wonder you may think it is over-engineering:-) Please, read when you have a time.

To support vue, we have to think of module.

1: var x = { methodA: function () {} };
2: export x;

When thinking of modules, a user of ctags may want x in both L1 and L2 to be tagged. x in the line 2 may be captured as a reference tag with role "exported". However, what "kind" should be assigned to the x in the line 2? The kind should be "variable" because it is defined as a variable in the line 1.

For assinging "variable" kind to "x" in line 2, the JavaScript parser must be able to remember the definition tag for "x". In other words, we need a symbol table.

I think the symbol table can be built on the corkQueue.

Before designing the symbol table, we have to make the JavsScript parser use the corkAPI.

I assume we will have the cork based JavaScript parser. Based on the assumption, I designed how modules, named exported and/or imported from the modules can be captured in consistent way. I will paste my note.

.. _javascript:

======================================================================
The JavaScript parser
======================================================================

Making tags for modules
---------------------------------------------------------------------

(This section is written by @masatake).

To tag names in "export" and "import" statements, following kinds, roles, and
fields are introduced.

Kinds

	"name"

		This kind mainly is used for N of "X as N" form. When an
		language object X is imported or exported with the different
		name N, the JavaScript parser assigns "name" kind to N.

		A "name" kind tag may have "exprots" or "imports" field.  The
		fields specifies X of "X as N" form. In other word, the fields
		specifies the real language object exported with the name N.

	"data"

		This kind represents something unknown data. e.g. when the
		JavaScript parser read a line, "import X from M;", the
		parser cannot know whether X is a variable, a constant, a class
		or a function because X is defined in M. In such case, the parser
		assigns the "data" kind to X as a placeholder.

		This kind mainly used for reference tags. If the parser finds
		definition for a "data", the parser can assign more specific
		kind instead.

		Depending on the statement where X is appeared, "data" kind tag
		takes one of three roles: "exported", "imported", or "passthrough".

		Depending on the statement where X is appeared, "data" kind tag may have
		some of three fields: "exportedAs", "importedAs", and/or "origin".
		"exportedAs" and "importedAs" fields are attached when tagging
		X of "X as N" form. "exportedAs:N" is attached to the "data" tag
		of X if the form is in a "export" statement. "importedAs:N" is attached
		to the "data" tag of X if the form is in a "import" statment.
		If X appears in "X as N from M" from, "origin:M" field is attached to
		X. Here M represents a module.

	"module"

		As name shown, it represents a referenced module.
		If the module name is appeared in an "export" statement,
		the parser assigns "aggregated" role to the "module" kind tag.
		If the module name is appeared in an "import" statement,
		the parser assigns "imported" role to the "module" kind tag.

Roles

	exported

	imported

	passthrough

	aggregated

Fields

	exportedAs
	exports
	origin
	importedAs
	imports


Handling "export" statement
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This section explains how the JavaScript parser tags names defined
and/or referenced in export statements.

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
about export statements.

export statements have following forms (quoted from the page):

.. code-block:: JavaScript

	// Exporting individual features
	export let name1, name2, ..., nameN; // also var, const
	export let name1 = ..., name2 = ..., ..., nameN; // also var, const
	export function functionName(){...}
	export class ClassName {...}

	// Export list
	export { name1, name2, ..., nameN };

	// Renaming exports
	export { variable1 as name1, variable2 as name2, ..., nameN };

	// Default exports
	export default expression;
	export default function (...) { ... } // also class, function*
	export default function name1(...) { ... } // also class, function*
	export { name1 as default, ... };

	// Aggregating modules
	export * from ...;
	export { name1, name2, ..., nameN } from ...;
	export { import1 as name1, import2 as name2, ..., nameN } from ...;
	export { default } from ...;

Exporting individual features
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Input:
.. code-block:: JavaScript

	I1: export let name1, name2, ..., nameN; // also var, const
	I2: export let nameA = ..., nameB = ..., ..., nameZ; // also var, const
	I3: export function functionName(){...}
	I4: export class ClassName {...}

Expected tags:
.. code-block:: tags

	T1: name1	input.mjs	/^...$/;"	line:I1	kind:variable	roles:def,exported
	...
	T2: nameA	input.mjs	/^...$/;"	line:I2	kind:variable	roles:def,exported
	...
	T3: functionName	input.mjs	/^...$/;"	line:I3	kind:function	roles:def,exported
	T4: className	input.mjs	/^...$/;"	line:I4	kind:class	roles:def,exported

These statements do two things at once: defining names and exporting
them.  The JavaScript parser makes a tag having both "def" and
"exported" roles for each name.

Export list
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Input:
.. code-block:: JavaScript

   // No definition for name2,... in the input.
   I1: var name1;
   ...
   I2: export { name1, name2, ..., nameN };

Expected tags:
.. code-block:: tags

	T1: name1	input.mjs	/^...$/;"	line:I1	kind:variable	roles:def
	...
	T2: name1	input.mjs	/^...$/;"	line:I2	kind:variable	roles:exported
	T3: name2	input.mjs	/^...$/;"	line:I2	kind:data	roles:exported
	...

This statement just refers names. The JavaScript parser emits
reference tags for the names. The issue is what kind should be
assigned to the tags. If the names are defined in the input (I1), the
JavaScript parser captures them (T1) before parsing the export
statement.  In this case, the parser can assign correct kinds for the
associated reference tags (T2).

If the parser cannot find some of the names in the input, the parser
assigns newly introduced "data" kind to the reference tags for the
names as placeholder. See T3, name2 has "data" kind because name2 is
not defined in the Input.


Renaming exports
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Input:
.. code-block:: JavaScript

	// No definition for variable2,... in the input.
	I1: var variable1;
	I2: export { variable1 as name1, variable2 as name2, ..., nameN };

Expected tags:
.. code-block:: tags

	T1: variable1	input.mjs	/^...$/;"	line:I1	kind:variable	roles:def
	T2: variable1	input.mjs	/^...$/;"	line:I2	kind:variable	roles:exported	exportedAs:name1
	T3: name1		input.mjs	/^...$/;"	line:I2	kind:name	roles:def	exports:variable1
	T4: variable2	input.mjs	/^...$/;"	line:I2	kind:data	roles:exported	exportedAs:name2
	T5: name2		input.mjs	/^...$/;"	line:I2	kind:name	roles:def	exports:variable2

In the form "V as N", V is indirectly exported with name N.  For the
form, the JavaScript parser makes a reference tag for V, and a
definition tag for N.

The way to assign a kind for V is the same as explained in "Export
list". For N, newly introduced "name" kind is assigned.

Newly introduced fields, "exports" and "exportedAs", are for
representing the relation between V and N.  "exports" field attached
to N represents what N exports.  "exportedAs" field attached to V
represents the name with which V is exported.

Default exports
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Input:
.. code-block:: JavaScript

	I1: export default identifier; // No definition for identifier in the input file
	I2: export default expression;
	I3: export default function (...) { ... } // also class, function*
	I4: export default function name1(...) { ... } // also class, function*
	I5: var name2; 	export { name2 as default, ... };
	I6: export { name3 as default, ... }; // No definition for name3 in input file


Expected tags:
.. code-block:: tags

	 T1: identifier input.mjs	/^...$/;"	line:I1	kind:data	roles:exported	exportedAs:default
	 T2: default	input.mjs	/^...$/;"	line:I1	kind:name	roles:def	exports:identifier
	 T3: AnonymousTag0	input.mjs	/^...$/;"	line:I2	kind:data	roles:def,exported exportedAs:default
	 T4: default	input.mjs	/^...$/;"	line:I2	kind:name	roles:def	exports:AnonymousTag0
	 T5: AnonymousTag1	input.mjs	/^...$/;"	line:I3	kind:function	roles:def,exported exportedAs:default
	 T6: default	input.mjs	/^...$/;"	line:I3	kind:name	roles:def	exports:AnonymousTag1
	 T7: name1	input.mjs	/^...$/;"	line:I4	kind:function	roles:def,exported exportedAs:default
	 T8: default	input.mjs	/^...$/;"	line:I4	kind:name	roles:def	exports:name1
	 T9: name2	input.mjs	/^...$/;"	line:I5	kind:variable	roles:def
	T10: name2	input.mjs	/^...$/;"	line:I5	kind:variable	roles:exported exportedAs:default
	T11: default	input.mjs	/^...$/;"	line:I5	kind:name	roles:def	exports:name2
	T12: name3	input.mjs	/^...$/;"	line:I6	kind:data	roles:exported exportedAs:default
	T13: default	input.mjs	/^...$/;"	line:I6	kind:name	roles:def	exports:name3

"export default" form allows exporting a language object without naming.

If the language object is an identifier (I1), the JavaScript parser
tags it with "exported" role (T1). In addition, the parser attaches
"exportedAs:default" field to the tag. If the definition for the name is
in the input file (I5), the kind for the definition tag of the name is
assigned to the reference having the same name. If the definition
for the name is not in the input file (I1), "data" kind is assigned to
the reference tag for the name (T1).

If the language object is not an identifier but has a name (I4), the
name is used for making a tag. The roles for the tag are "def" and
"exported" (T7). "exportedAs:default" field is also attached to the
tag.

If the language object is not an identifier and doesn't have a name
(I2, I3), anonymous name generated by ctags is used as name for
tagging (T3, T5).

In any forms in "Default exports", the JavaScript parser emits
"default" with "name" kind for each export statement.  Strictly
speaking, "default" is a keyword of JavaScript language, not a
defined name. However, the tag for "default" may help a client
tool find exported language objects from a input file quickly.

A "default" tag has "exports" field representing what language
object indirectly exported via the "default" keyword.


Aggregating modules
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Input:
.. code-block:: JavaScript

	I1: export * from MJS0;
	I2: export { name1, name2, ..., nameN } from MJS1;
	I3: export { import1 as nameA, import2 as nameB, ..., nameZ } from MJS2;
	I4: export { importX as default } from MJS3;
	I5: export { default } from MJS4;

Expected tags:
.. code-block:: tags

	 T1: MJS0	input.mjs	/^...$/;"	line:I1	kind:module	roles:aggregated
   	 T2: *	input.mjs	/^...$/;"	line:I1	kind:data	roles:passthrough origin:MJS0
	 T3: MJS1	input.mjs	/^...$/;"	line:I2	kind:module	roles:aggregated
	 T4: name1	input.mjs	/^...$/;"	line:I2	kind:data	roles:passthrough origin:MJS1
	 T5: MJS2	input.mjs	/^...$/;"	line:I3	kind:module	roles:aggregated
	 T6: import1	input.mjs	/^...$/;"	line:I3	kind:data	roles:passthrough origin:MJS2 exportedAs:name1
	 T7: nameA	input.mjs	/^...$/;"	line:I3	kind:name	roles:def exports:import1
	 T8: MJS3	input.mjs	/^...$/;"	line:I4	kind:module	roles:aggregated
	 T9: importX	input.mjs	/^...$/;"	line:I4	kind:data	roles:passthrough	origin:MJS3	exportedAs:default
	T10: default input.mjs	/^...$/;"	line:I4	kind:name	roles:def exports:importX
	T11: MJS4	input.mjs	/^...$/;"	line:I5	kind:module	roles:aggregated

	???
	T12: AnonymousTag0	input.mjs	/^...$/;"	line:I5	kind:data	roles:passthrough	origin:MJS4 exportedAs:default
	T13: default	input.mjs	/^...$/;"	line:I5	kind:name	roles:def exports:AnonymousTag0
	or
	T12: default	input.mjs	/^...$/;"	line:I5	kind:name	roles:passthrough	origin:MJS4 exports:default
	or
	T12: default	input.mjs	/^...$/;"	line:I5	kind:data	roles:passthrough	origin:MJS4 exportedAs:default
	T13: default	input.mjs	/^...$/;"	line:I5	kind:name	roles:def exports:default
	or
	T12: default	input.mjs	/^...$/;"	line:I5	kind:name	roles:def,passthrough exports:default	origin:MJS4 exportedAs:default

In the forms, language objects exported from a module are exported
from the input. "passthrough" role is introduced for representing the
language objects. Combining "imported" and "exported" roles cannot
represent what "passthrough" role represents because the language
objects exported from the external module are not imported to the
input.

"module" kind is introduced to represent the language objects
specified in "from ..." form of export statement. The "module" kind is
also used to represent the language objects specified in import
statements. "aggregated" role is for representing "from ..." form.

"origin" field is attached to tags having "passthrough" role. It
represents the module where the language object comes from.

The kind for passthrough'ed language objects is "data" because the
definitions for the language objects may be in the external module,
not in the input.


Handling "import" statment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section explains how the JavaScript parser tags names defined
and/or referenced in import statements.

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
about import statements.

import statements have following forms (quoted from the page):
.. code-block:: JavaScript

	 I1: import defaultExport0 from "module-name1";
	 I2: import * as name0 from "module-name2";
	 I3: import { export0 } from "module-name3";
	 I4: import { export1 as alias1 } from "module-name4";
	 I5: import { export2 , export3 } from "module-name5";
	 I6: import { export4 , export5 as alias2 , [...] } from "module-name6";
	 I7: import defaultExport1, { export6 [ , [...] ] } from "module-name7";
	 I8: import defaultExport2, * as name1 from "module-name8";
	 I9: import "module-name9";
	I10: var promise = import(module-name10);

.. code-block:: tags


	 T1: module-name1	input.mjs	/^...$/;"	line:I1	kind:module	roles:imported

	 ???
	 T2: default	input.mjs	/^...$/;"	line:I1	kind:data	roles:imported	importedAs:defaultExport	origin:module-name1
	 T3: defaultExport0	input.mjs	/^...$/;"	line:I1	kind:name	roles:def	imports:default
	 or
	 T2: defaultExport0	input.mjs	/^...$/;"	line:I1	kind:name	roles:def	imports:default	origin:module-name1

	 T4: module-name2	input.mjs	/^...$/;"	line:I2	kind:module	roles:imported
	 T4: *	input.mjs	/^...$/;"	line:I2	kind:data	roles:imported	importedAs:name0	origin:module-name2
	 T5: name0	input.mjs	/^...$/;"	line:I2	kind:name	roles:def	imports:*
	 T6: module-name3	input.mjs	/^...$/;"	line:I3	kind:module	roles:imported
	 T7: export0	input.mjs	/^...$/;"	line:I3	kind:data	roles:imported	origin:module-name3
	 T8: module-name4	input.mjs	/^...$/;"	line:I4	kind:module	roles:imported
	 T9: export1	input.mjs	/^...$/;"	line:I4	kind:data	roles:imported	importedAs:alias1 origin:module-name4
	T10: alias1	input.mjs	/^...$/;"	line:I4	kind:name	roles:def imports:export1
	T11: module-name5	input.mjs	/^...$/;"	line:I5	kind:module	roles:imported
	T12: export2	input.mjs	/^...$/;"	line:I5	kind:data	roles:imported	origin:module-name5
	T13: export3	input.mjs	/^...$/;"	line:I5	kind:data	roles:imported	origin:module-name5
	T14: module-name6	input.mjs	/^...$/;"	line:I6	kind:module	roles:imported
	T15: export4	input.mjs	/^...$/;"	line:I6	kind:data	roles:imported	origin:module-name6
	T16: export5	input.mjs	/^...$/;"	line:I6	kind:data	roles:imported	importedAs:alias2	origin:module-name6
	T17: alias2	input.mjs	/^...$/;"	line:I6	kind:name	roles:def	imports:export5
	T18: module-name7	input.mjs	/^...$/;"	line:I7	kind:module	roles:imported
	T19: defaultExport1	input.mjs	/^...$/;"	line:I7	kind:name	roles:def	imports:default	origin:module-name7
	T20: export6	input.mjs	/^...$/;"	line:I7	kind:data	roles:imported	origin:module-name7
	T21: module-name8	input.mjs	/^...$/;"	line:I8	kind:module	roles:imported
	T22: defaultExport2	input.mjs	/^...$/;"	line:I8	kind:name	roles:def	imports:default	origin:module-name8
	T23: *	input.mjs	/^...$/;"	line:I8	kind:data	roles:imported	importedAs:name1	origin:module-name8
	T24: name1	input.mjs	/^...$/;"	line:I8	kind:name	roles:def	imports:*
	T25: module-name9	input.mjs	/^...$/;"	line:I8	kind:module	roles:imported


  Two use cases are considered when designing how JavaScript tags the
names in the statements.

Jump to definition

	A JavaScript source file X imports module Y. A name y exported
	from Y is used in X. A user of ctags reading X wants to know the
	definition of y. The JavaScript of ctags should provide following
	tags for support this use case:

	the definition tag for y if y is exported directly, or the
	definition tag for y that has exprots:z field if z is exported
	indirectly.

	In the later case, one more step is needed to jump the place where
	y points. Find the definition tag for z.

List all names exported from a module

	Symbol completion feture may need this.
	The names are tagged as reference tags with exported role.

masatake avatar Jun 14 '19 08:06 masatake