diff --git a/Tmain/extras-long.d/stdout-expected.txt b/Tmain/extras-long.d/stdout-expected.txt index b456ce591f..0daa85c82a 100644 --- a/Tmain/extras-long.d/stdout-expected.txt +++ b/Tmain/extras-long.d/stdout-expected.txt @@ -7,6 +7,7 @@ p pseudo no NONE no Include pseudo tags q qualified no NONE no Include an extra class-qualified tag entry for each tag r reference no NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag no NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class @@ -25,6 +26,7 @@ p pseudo yes NONE no Include pseudo tags q qualified no NONE no Include an extra class-qualified tag entry for each tag r reference no NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag no NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class @@ -43,6 +45,7 @@ p pseudo yes NONE no Include pseudo tags q qualified no NONE no Include an extra class-qualified tag entry for each tag r reference no NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag no NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class @@ -61,6 +64,7 @@ p pseudo yes NONE no Include pseudo tags q qualified no NONE no Include an extra class-qualified tag entry for each tag r reference yes NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag no NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class @@ -79,6 +83,7 @@ p pseudo yes NONE no Include pseudo tags q qualified yes NONE no Include an extra class-qualified tag entry for each tag r reference yes NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag no NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class diff --git a/Tmain/json-output-format.d/stdout-expected.txt b/Tmain/json-output-format.d/stdout-expected.txt index b8e96b9d5e..260b9fd5c9 100644 --- a/Tmain/json-output-format.d/stdout-expected.txt +++ b/Tmain/json-output-format.d/stdout-expected.txt @@ -20,6 +20,7 @@ {"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "fileScope", "pattern": "Include tags of file scope"} {"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "guest", "pattern": "Include tags generated by guest parsers"} {"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "inputFile", "pattern": "Include an entry for the base file name of every input file"} +{"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "nulltag", "pattern": "Include tags with empty strings as their names"} {"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "pseudo", "pattern": "Include pseudo tags"} {"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "qualified", "pattern": "Include an extra class-qualified tag entry for each tag"} {"_type": "ptag", "name": "TAG_EXTRA_DESCRIPTION", "path": "reference", "pattern": "Include reference tags"} diff --git a/Tmain/kind-abnormal-spec.d/stdout-expected.txt b/Tmain/kind-abnormal-spec.d/stdout-expected.txt index 39beadb22a..7beb40ffad 100644 --- a/Tmain/kind-abnormal-spec.d/stdout-expected.txt +++ b/Tmain/kind-abnormal-spec.d/stdout-expected.txt @@ -12,6 +12,8 @@ r emit a tag with multi roles R emit a tag with multi roles(disabled by default) [off] f tag for testing field: n trigger notice output +z emit a tag having an empty string +Z don't emit a tag having an empty string # list kinds-full #LETTER NAME ENABLED REFONLY NROLES MASTER DESCRIPTION @@ -22,12 +24,14 @@ L ThisShouldNotBePrintedKindNameMustBeGiven yes no 0 NONE N nothingSpecial yes no 0 NONE emit a normal tag Q quit yes no 0 NONE stop the parsing R rolesDisabled no yes 2 NONE emit a tag with multi roles(disabled by default) +Z dontEmitNullTag yes no 0 NONE don't emit a tag having an empty string b broken tag yes no 1 NONE name with unwanted characters d disabled no no 2 NONE a kind disabled by default e enabled yes no 2 NONE a kind enabled by default f fieldMaker yes no 0 NONE tag for testing field: n triggerNotice yes no 0 NONE trigger notice output r roles yes yes 4 NONE emit a tag with multi roles +z emitNullTag yes no 0 NONE emit a tag having an empty string # +K abnormal kindDefinition testing (no letter) input.x /^@$/;" no letter diff --git a/Tmain/list-extras.d/stdout-expected.txt b/Tmain/list-extras.d/stdout-expected.txt index 44195a349b..637a4c47d3 100644 --- a/Tmain/list-extras.d/stdout-expected.txt +++ b/Tmain/list-extras.d/stdout-expected.txt @@ -7,6 +7,7 @@ p pseudo yes NONE no Include pseudo tags q qualified yes NONE no Include an extra class-qualified tag entry for each tag r reference yes NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag yes NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class @@ -25,6 +26,7 @@ p pseudo yes NONE no Include pseudo tags q qualified yes NONE no Include an extra class-qualified tag entry for each tag r reference yes NONE no Include reference tags s subparser yes NONE no Include tags generated by subparsers +z nulltag yes NONE no Include tags with empty strings as their names - canonicalizedName yes Automake no Include canonicalized object name like libctags_a - linkName no Fortran no Linking name used in foreign languages - implicitClass no GDScript no Include tag for the implicitly defined unnamed class @@ -43,3 +45,4 @@ p pseudo no NONE no Include pseudo tags q qualified no NONE no Include an extra class-qualified tag entry for each tag r reference no NONE no Include reference tags s subparser no NONE no Include tags generated by subparsers +z nulltag no NONE no Include tags with empty strings as their names diff --git a/Tmain/list-output-formats.d/exit-expected.txt b/Tmain/list-output-formats.d/exit-expected.txt new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/Tmain/list-output-formats.d/exit-expected.txt @@ -0,0 +1 @@ +0 diff --git a/Tmain/list-output-formats.d/run.sh b/Tmain/list-output-formats.d/run.sh new file mode 100755 index 0000000000..c3bc8e15a8 --- /dev/null +++ b/Tmain/list-output-formats.d/run.sh @@ -0,0 +1,7 @@ +# Copyright: 2024 Masatake YAMATO +# License: GPL-2 + +CTAGS=$1 + +$CTAGS --quiet --options=NONE --machinable --with-list-header \ + --list-output-formats | grep -v '^json' diff --git a/Tmain/list-output-formats.d/stderr-expected.txt b/Tmain/list-output-formats.d/stderr-expected.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tmain/list-output-formats.d/stdout-expected.txt b/Tmain/list-output-formats.d/stdout-expected.txt new file mode 100644 index 0000000000..32b8280768 --- /dev/null +++ b/Tmain/list-output-formats.d/stdout-expected.txt @@ -0,0 +1,5 @@ +#OFORMAT DEFAULT AVAILABLE NULLTAG +e-ctags no yes no +etags no yes no +u-ctags yes yes no +xref no yes yes diff --git a/Tmain/nulltag-extra.d/input.cst b/Tmain/nulltag-extra.d/input.cst new file mode 100644 index 0000000000..d750d09a34 --- /dev/null +++ b/Tmain/nulltag-extra.d/input.cst @@ -0,0 +1,3 @@ +Z +z + diff --git a/Tmain/nulltag-extra.d/run.sh b/Tmain/nulltag-extra.d/run.sh new file mode 100644 index 0000000000..8cd2195ccf --- /dev/null +++ b/Tmain/nulltag-extra.d/run.sh @@ -0,0 +1,30 @@ +# Copyright: 2024 Masatake YAMATO +# License: GPL-2 + +CTAGS=$1 + +. ../utils.sh + +# The order of stdout and stderr lines is not stable. +exit_if_win32 "$CTAGS" + +# is_feature_available $CTAGS json + +O="--options=NONE --language-force=CTagsSelfTest" + +for fmt in xref; do + echo "# no extra ($fmt)" + ${CTAGS} $O -o - --output-format="$fmt" input.cst 2>&1 + + echo "# drop '0' extra ($fmt)" + ${CTAGS} $O -o - --output-format="$fmt" --extras=-z input.cst 2>&1 + + echo "# drop '{nulltag}' extra ($fmt)" + ${CTAGS} $O -o - --output-format="$fmt" --extras=-'{nulltag}' input.cst 2>&1 + + echo '# with --extras=+0 ($fmt)' + ${CTAGS} $O -o - --output-format="$fmt" --extras=+z input.cst 2>&1 + + echo "# with --extras=+{nulltag}' ($fmt)" + ${CTAGS} $O -o - --output-format="$fmt" --extras=+'{nulltag}' input.cst 2>&1 +done | sed -e 's/\.exe//' diff --git a/Tmain/nulltag-extra.d/stderr-expected.txt b/Tmain/nulltag-extra.d/stderr-expected.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tmain/nulltag-extra.d/stdout-expected.txt b/Tmain/nulltag-extra.d/stdout-expected.txt new file mode 100644 index 0000000000..af4a7169c2 --- /dev/null +++ b/Tmain/nulltag-extra.d/stdout-expected.txt @@ -0,0 +1,17 @@ +# no extra (xref) +ctags: Notice: No options will be read from files or environment +ctags: Notice: ignoring null tag in input.cst(line: 1, language: CTagsSelfTest) +# drop '0' extra (xref) +ctags: Notice: No options will be read from files or environment +ctags: Notice: ignoring null tag in input.cst(line: 1, language: CTagsSelfTest) +# drop '{nulltag}' extra (xref) +ctags: Notice: No options will be read from files or environment +ctags: Notice: ignoring null tag in input.cst(line: 1, language: CTagsSelfTest) +# with --extras=+0 ($fmt) +ctags: Notice: No options will be read from files or environment +ctags: Notice: ignoring null tag in input.cst(line: 1, language: CTagsSelfTest) + emitNullTag 2 input.cst z +# with --extras=+{nulltag}' (xref) +ctags: Notice: No options will be read from files or environment +ctags: Notice: ignoring null tag in input.cst(line: 1, language: CTagsSelfTest) + emitNullTag 2 input.cst z diff --git a/Units/parser-javascript.r/js-extract-empty-property-in-json.d/args.ctags b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/args.ctags new file mode 100644 index 0000000000..53687d339e --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/args.ctags @@ -0,0 +1,4 @@ +--sort=no +--extras=+{nulltag} +--fields=+{extras} +--output-format=json diff --git a/Units/parser-javascript.r/js-extract-empty-property-in-json.d/expected.tags-json b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/expected.tags-json new file mode 100644 index 0000000000..bdabdd7014 --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/expected.tags-json @@ -0,0 +1,2 @@ +{"_type": "tag", "name": "", "path": "input.js", "pattern": "/^var variable = { \"\": \"value\" };$/", "kind": "property", "scope": "variable", "scopeKind": "variable", "extras": "nulltag"} +{"_type": "tag", "name": "variable", "path": "input.js", "pattern": "/^var variable = { \"\": \"value\" };$/", "kind": "variable"} diff --git a/Units/parser-javascript.r/js-extract-empty-property-in-json.d/features b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/features new file mode 100644 index 0000000000..3c840093b7 --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/features @@ -0,0 +1 @@ +json diff --git a/Units/parser-javascript.r/js-extract-empty-property-in-json.d/input.js b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/input.js new file mode 100644 index 0000000000..b2d76001f5 --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property-in-json.d/input.js @@ -0,0 +1 @@ +var variable = { "": "value" }; diff --git a/Units/parser-javascript.r/js-extract-empty-property.d/args.ctags b/Units/parser-javascript.r/js-extract-empty-property.d/args.ctags new file mode 100644 index 0000000000..2b556d2229 --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property.d/args.ctags @@ -0,0 +1,5 @@ +--sort=no +--extras=+{nulltag} +--fields=+{extras} +-x +--_xformat=%-16N %-10K %4n input.js %C diff --git a/Units/parser-javascript.r/js-extract-empty-property.d/expected.tags-x b/Units/parser-javascript.r/js-extract-empty-property.d/expected.tags-x new file mode 100644 index 0000000000..154efffc04 --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property.d/expected.tags-x @@ -0,0 +1,2 @@ + property 1 input.js var variable = { "": "value" }; +variable variable 1 input.js var variable = { "": "value" }; diff --git a/Units/parser-javascript.r/js-extract-empty-property.d/input.js b/Units/parser-javascript.r/js-extract-empty-property.d/input.js new file mode 100644 index 0000000000..b2d76001f5 --- /dev/null +++ b/Units/parser-javascript.r/js-extract-empty-property.d/input.js @@ -0,0 +1 @@ +var variable = { "": "value" }; diff --git a/Units/parser-javascript.r/js-skip-empty-property.d/args.ctags b/Units/parser-javascript.r/js-skip-empty-property.d/args.ctags new file mode 100644 index 0000000000..5ee5f79f70 --- /dev/null +++ b/Units/parser-javascript.r/js-skip-empty-property.d/args.ctags @@ -0,0 +1 @@ +--sort=no diff --git a/Units/parser-javascript.r/js-skip-empty-property.d/expected.tags b/Units/parser-javascript.r/js-skip-empty-property.d/expected.tags new file mode 100644 index 0000000000..de90d0b875 --- /dev/null +++ b/Units/parser-javascript.r/js-skip-empty-property.d/expected.tags @@ -0,0 +1 @@ +variable input.js /^var variable = { "": "value" };$/;" v diff --git a/Units/parser-javascript.r/js-skip-empty-property.d/input.js b/Units/parser-javascript.r/js-skip-empty-property.d/input.js new file mode 100644 index 0000000000..b2d76001f5 --- /dev/null +++ b/Units/parser-javascript.r/js-skip-empty-property.d/input.js @@ -0,0 +1 @@ +var variable = { "": "value" }; diff --git a/docs/internal.rst b/docs/internal.rst index 421158c1ae..d17c98e955 100644 --- a/docs/internal.rst +++ b/docs/internal.rst @@ -964,6 +964,26 @@ Refer `peg/valink.peg `_ as a sample of a parser using PackCC. +Null tags +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*Null tag* is a tag having an empty string as its name. + +Universal Ctags supports null tags cautiously. +See the description for ``nulltag``/``z`` extra in :ref:`ctags(1) `. + +If you want to make a null tag, set 1 to ``allowNullTag`` of +``tagEtnryInfo`` before calling ``makeTagEntry``. ``makeTagEntry`` +evaluates the member. The following pseudo code doesn't work: + +.. code-block:: C + + tagEntryInfo e; + /* ... */ + int corkIndex = makeTagEntry (&e); + /* ... */ + tagEntryInfo *p = getEntryInCorkQueue (corkIndex); + p->allowNullTag = 1; + Automatic parser guessing (TBW) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/man/ctags.1.rst b/docs/man/ctags.1.rst index 9d70ff8953..4709e41635 100644 --- a/docs/man/ctags.1.rst +++ b/docs/man/ctags.1.rst @@ -318,6 +318,8 @@ Output Format Options the ctags executable is built with ``libjansson``. See :ref:`ctags-json-output(5) ` for more about ``json`` format. + See also ``--list-output-formats``. + ``-e`` Same as ``--output-format=etags``. Enable etags mode, which will create a tag file for use with the Emacs @@ -1236,6 +1238,14 @@ Listing Options definition. See :ref:`ctags-optlib(7) `. +``--list-output-formats`` + Lists the output formats that can be used in ``--output-format`` option. + + ``NULLTAG`` column represetns whether the format supports *null tags* or + not. See ``nulltag``/``z`` in "`Extras`_" about the null tags. + + (since verison 6.2.0) + ``--list-params[=(|all)]`` Lists the parameters for either the specified ** or ``all`` languages, and then exits. @@ -1835,6 +1845,8 @@ roles of tags to include in the output file for a particular language. Inquire the output of "``ctags --list-roles``" for the list of roles. +.. _extras: + Extras ~~~~~~ @@ -1927,6 +1939,25 @@ The meaning of major extras is as follows (long-name flag/one-letter flag): The etags mode enables the ``Unknown`` parser implicitly. +``nulltag``/``z`` + Include tags (*null tags*) having empty strings as their names. + Generally speaking, trying to make a null tag is a sign of a parser bug + or broken input. ctags warns such trying or throws the + null tag away. To suppress the warnings, use ``--quiet`` option. + + On the other hand, null tags are valid in some languages. + Consider ``{"": val}`` in a JavaScript sourece code. The empty string is + valid as a key. If a parser intentionally makes a null tag (a valid null tag), + ctags doesn't warn but discard it by default. + + The discards are due because some output formats may not consider null tags. + + With ``nulltag``/``z`` extra, you can force ctags to emit the nulltags. This extra + is effective only if the output format supports null tags. ``--list-output-formats`` + option tells you which output formats support null tags. + + (since version 6.2.0) + ``pseudo``/``p`` Include pseudo-tags. Enabled by default unless the tag file is written to standard output. See :ref:`ctags-client-tools(7) ` about @@ -2273,6 +2304,8 @@ The official Universal Ctags web site at: https://ctags.io/ Also ``ex(1)``, ``vi(1)``, ``elvis(1)``, or, better yet, ``vim(1)``, the official editor of ctags. For more information on ``vim(1)``, see the Vim web site at: https://www.vim.org/ +About the file format for ``TAGS``, see `emacs git +`_. AUTHOR ------ diff --git a/docs/news/HEAD.rst b/docs/news/HEAD.rst index 35b8034358..c209e7556e 100644 --- a/docs/news/HEAD.rst +++ b/docs/news/HEAD.rst @@ -5,6 +5,20 @@ Changes in 6.?.0 New and extended options and their flags --------------------------------------------------------------------- +``--list-output-formats`` option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +See :ref:`option_listing` in :ref:`ctags(1) `. + +``nulltag``/``z`` extra +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Universal Ctags now supports tags (*null tags*) having empty strings as their names. +See :ref:`extras` in :ref:`ctags(1) `. + +.. note:: + + * ``libreadtags`` and ``readtags`` do not support the null tags yet. + * ``json`` and ``xref`` output formats support the null tags. + Incompatible changes --------------------------------------------------------------------- diff --git a/docs/output-format.rst b/docs/output-format.rst index d21181e249..32469142fc 100644 --- a/docs/output-format.rst +++ b/docs/output-format.rst @@ -23,11 +23,8 @@ Supported *format* are ``u-ctags``, ``e-ctags``, ``etags``, ``xref``, and ``json ``etags`` Output format for Emacs etags. - ``--output-format=etags`` can be abbreviated as ``-e``. - See `emacs git - `_ for - details. + See the description of ``-e`` option in :ref:`ctags(1) `. ``xref`` A tabular, human-readable cross reference (xref) format. @@ -38,7 +35,7 @@ Supported *format* are ``u-ctags``, ``e-ctags``, ``etags``, ``xref``, and ``json ``json`` JSON format. - See section :ref:`output-json` for details. + See :ref:`ctags-client-tools(7) `. ********* @@ -47,4 +44,3 @@ Supported *format* are ``u-ctags``, ``e-ctags``, ``etags``, ``xref``, and ``json output-tags.rst output-xref.rst - output-json.rst diff --git a/docs/output-json.rst b/docs/output-json.rst deleted file mode 100644 index 7152e014ec..0000000000 --- a/docs/output-json.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _output-json: - -====================================================================== -JSON output -====================================================================== -See :ref:`ctags-client-tools(7) `. diff --git a/main/colprint.c b/main/colprint.c index 3e935d4070..e4f74d97da 100644 --- a/main/colprint.c +++ b/main/colprint.c @@ -19,9 +19,10 @@ enum colprintJustification { - COLPRINT_LEFT, /* L:... */ - COLPRINT_RIGHT, /* R:... */ - COLPRINT_LAST, + COLPRINT_LEFT = 0, + COLPRINT_RIGHT = 1, + COLPRINT_JUST = 1 << 0, + COLPRINT_LAST = 1 << 1, /* private use */ }; struct colprintHeaderColumn { @@ -101,7 +102,7 @@ struct colprintTable *colprintTableNew (const char* columnHeader, ... /* NULL TE struct colprintHeaderColumn *last_col = ptrArrayLast (table->header); if (last_col) - last_col->justification = COLPRINT_LAST; + last_col->justification |= COLPRINT_LAST; return table; } @@ -130,7 +131,7 @@ static void colprintColumnPrintGeneric (vString *column, struct colprintHeaderCo if (machinable) { fputs (vStringValue (column), fp); - if (spec->justification != COLPRINT_LAST) + if (! (spec->justification & COLPRINT_LAST)) fputc ('\t', fp); } else @@ -139,17 +140,16 @@ static void colprintColumnPrintGeneric (vString *column, struct colprintHeaderCo size_t colLen = vStringLength (column); if (colLen < maxWidth) padLen = maxWidth - colLen; - if (spec->justification == COLPRINT_LEFT - || spec->justification == COLPRINT_LAST) + if ((spec->justification & COLPRINT_JUST) == COLPRINT_LEFT) { fputs (vStringValue (column), fp); - if (spec->justification != COLPRINT_LAST) + if (! (spec->justification & COLPRINT_LAST)) { fillWithWhitespaces (padLen, fp); fputc (' ', fp); } } - else + else if ((spec->justification & COLPRINT_JUST) == COLPRINT_RIGHT) { fillWithWhitespaces (padLen, fp); fputs (vStringValue (column), fp); diff --git a/main/entry.c b/main/entry.c index 6795c1b824..89713624e2 100644 --- a/main/entry.c +++ b/main/entry.c @@ -1762,7 +1762,7 @@ static void buildFqTagCache (tagEntryInfo *const tag) getTagScopeInformation (tag, NULL, NULL); } -static void writeTagEntry (const tagEntryInfo *const tag) +static void writeTagEntry (tagEntryInfo *const tag) { int length = 0; @@ -1770,11 +1770,20 @@ static void writeTagEntry (const tagEntryInfo *const tag) DebugStatement ( debugEntry (tag); ) + if (isTagExtraBitMarked(tag, XTAG_NULLTAG)) + { + if (!writerCanPrintNullTag()) + return; + + if (!isXtagEnabled(XTAG_NULLTAG)) + return; + } + #ifdef _WIN32 if (getFilenameSeparator(Option.useSlashAsFilenameSeparator) == FILENAME_SEP_USE_SLASH) { - Assert (((const tagEntryInfo *)tag)->inputFileName); - char *c = (char *)(((tagEntryInfo *const)tag)->inputFileName); + Assert (tag->inputFileName); + char *c = (char *)(tag->inputFileName); while (*c) { if (*c == PATH_SEPARATOR) @@ -1791,7 +1800,7 @@ static void writeTagEntry (const tagEntryInfo *const tag) && !tag->skipAutoFQEmission) { /* const is discarded to update the cache field of TAG. */ - buildFqTagCache ( (tagEntryInfo *const)tag); + buildFqTagCache (tag); } length = writerWriteTag (TagFile.mio, tag); @@ -1926,7 +1935,7 @@ extern int makePlaceholder (const char *const name) return makeTagEntry (&e); } -extern int makeTagEntry (const tagEntryInfo *const tag) +extern int makeTagEntry (tagEntryInfo *const tag) { int r = CORK_NIL; Assert (tag->name != NULL); @@ -1938,11 +1947,18 @@ extern int makeTagEntry (const tagEntryInfo *const tag) if (tag->name [0] == '\0' && (!tag->placeholder)) { - if (!doesInputLanguageAllowNullTag()) + if (! tag->allowNullTag) + { error (NOTICE, "ignoring null tag in %s(line: %lu, language: %s)", getInputFileName (), tag->lineNumber, getLanguageName (tag->langType)); - goto out; + goto out; + } + + /* writeTagEntry decides whether ctags emits this tag or not. + * At this point, we just mark the tag as a null tag. */ + if (! tag->placeholder) + markTagExtraBit(tag, XTAG_NULLTAG); } if (TagFile.cork) @@ -2082,6 +2098,8 @@ static void initTagEntryFull (tagEntryInfo *const e, const char *const name, if (isParserMarkedNoEmission ()) e->placeholder = 1; + + e->allowNullTag = doesLanguageAllowNullTag (e->langType); } extern void initTagEntry (tagEntryInfo *const e, const char *const name, diff --git a/main/entry.h b/main/entry.h index 19ac0865da..ede0925e7c 100644 --- a/main/entry.h +++ b/main/entry.h @@ -76,6 +76,13 @@ struct sTagEntryInfo { * Set in the cork queue; don't touch this.*/ unsigned int boundaryInfo: 2; /* info about nested input stream */ unsigned int inIntevalTab:1; + unsigned int allowNullTag:1; /* allow a tag with an empty string. + * To allow your parser to emit null tags without + * setting this per-entry allowNullTag, + * set parserDefinition::allowNullTag instead. + * + * Set this member before calling makeTagEntry. + */ unsigned long lineNumber; /* line number of tag; use updateTagLine() for updating this member. */ @@ -164,7 +171,7 @@ typedef bool (* entryForeachFunc) (int corkIndex, /* * FUNCTION PROTOTYPES */ -extern int makeTagEntry (const tagEntryInfo *const tag); +extern int makeTagEntry (tagEntryInfo *const tag); extern void initTagEntry (tagEntryInfo *const e, const char *const name, int kindIndex); extern void initRefTagEntry (tagEntryInfo *const e, const char *const name, diff --git a/main/options.c b/main/options.c index 7888250dd7..79a86920a3 100644 --- a/main/options.c +++ b/main/options.c @@ -440,6 +440,8 @@ static optionDescription LongOptionDescription [] = { {1,0," Output list of language mappings (both extensions and patterns)."}, {1,0," --list-mline-regex-flags"}, {1,0," Output list of flags which can be used in a multiline regex parser definition."}, + {1,0," --list-output-formats"}, + {1,0," Output list of output formats."}, {1,0," --list-params[=(|all)]"}, {1,0," Output list of language parameters. This works with --machinable."}, {0,0," --list-pseudo-tags"}, @@ -2314,6 +2316,13 @@ static void processListOperators (const char *const option CTAGS_ATTR_UNUSED, exit (0); } +static void processListOutputFormatsOption(const char *const option CTAGS_ATTR_UNUSED, + const char *const parameter CTAGS_ATTR_UNUSED) +{ + printOutputFormats (localOption.withListHeader, localOption.machinable, stdout); + exit (0); +} + static void freeSearchPathList (searchPathList** pathList) { stringListClear (*pathList); @@ -2406,20 +2415,42 @@ static void processOutputFormat (const char *const option CTAGS_ATTR_UNUSED, if (parameter [0] == '\0') error (FATAL, "no output format name supplied for \"%s\"", option); - if (strcmp (parameter, "u-ctags") == 0) - ; - else if (strcmp (parameter, "e-ctags") == 0) - setTagWriter (WRITER_E_CTAGS, NULL); - else if (strcmp (parameter, "etags") == 0) + writerType t = getWrierForOutputFormat (parameter); + if (t == WRITER_DEFAULT) + return; + + switch (t) + { + case WRITER_UNAVAILABLE: + error (FATAL, + "the output format \"%s\" is not available on this platform", + parameter); + break; + case WRITER_UNKNOWN: + error (FATAL, "unknown output format name supplied for \"%s=%s\"", + option, parameter); + break; + + case WRITER_U_CTAGS: + case WRITER_E_CTAGS: + setTagWriter (t, NULL); + break; + case WRITER_ETAGS: setEtagsMode (); - else if (strcmp (parameter, "xref") == 0) + break; + case WRITER_XREF: setXrefMode (); + break; #ifdef HAVE_JANSSON - else if (strcmp (parameter, "json") == 0) + case WRITER_JSON: setJsonMode (); + break; #endif - else - error (FATAL, "unknown output format name supplied for \"%s=%s\"", option, parameter); + case WRITER_CUSTOM: + case WRITER_COUNT: /* Suppress warnings that gcc reports */ + AssertNotReached (); + break; + } } static void processPseudoTags (const char *const option CTAGS_ATTR_UNUSED, @@ -2877,6 +2908,7 @@ static parametricOption ParametricOptions [] = { { "list-map-extensions", processListMapExtensionsOption, true, STAGE_ANY }, { "list-map-patterns", processListMapPatternsOption, true, STAGE_ANY }, { "list-mline-regex-flags", processListMultilineRegexFlagsOptions, true, STAGE_ANY }, + { "list-output-formats", processListOutputFormatsOption, true, STAGE_ANY }, { "list-params", processListParametersOption, true, STAGE_ANY }, { "list-pseudo-tags", processListPseudoTagsOptions, true, STAGE_ANY }, { "list-regex-flags", processListRegexFlagsOptions, true, STAGE_ANY }, diff --git a/main/parse.c b/main/parse.c index eb3becae64..0fa7076253 100644 --- a/main/parse.c +++ b/main/parse.c @@ -5550,6 +5550,8 @@ typedef enum { K_ROLES_DISABLED, K_FIELD_TESTING, K_TRIGGER_NOTICE, + K_EMIT_NULL_TAG, + K_DONT_EMIT_NULL_TAG, KIND_COUNT } CTST_Kind; @@ -5631,6 +5633,8 @@ static kindDefinition CTST_Kinds[KIND_COUNT] = { .referenceOnly = true, ATTACH_ROLES (CTST_RolesDisabledKindRoles)}, {true, 'f', "fieldMaker", "tag for testing field:" }, {true, 'n', "triggerNotice", "trigger notice output"}, + {true, 'z', "emitNullTag", "emit a tag having an empty string"}, + {true, 'Z', "dontEmitNullTag", "don't emit a tag having an empty string"}, }; typedef enum { @@ -5820,6 +5824,16 @@ static void createCTSTTags (void) case K_TRIGGER_NOTICE: notice ("notice output for testing: %s", CTST_Kinds [i].name); break; + case K_EMIT_NULL_TAG: + initTagEntry (&e, "", i); + e.allowNullTag = 1; + makeTagEntry (&e); + break; + case K_DONT_EMIT_NULL_TAG: + initTagEntry (&e, "", i); + e.allowNullTag = 0; + makeTagEntry (&e); + break; } if (quit) diff --git a/main/parse.h b/main/parse.h index 6f3051b619..bd67c9621a 100644 --- a/main/parse.h +++ b/main/parse.h @@ -99,7 +99,7 @@ struct sParserDefinition { unsigned int versionCurrent; unsigned int versionAge; - kindDefinition* kindTable; /* tag kinds handled by parser */ + kindDefinition* kindTable; /* tag kinds handled by parser */ unsigned int kindCount; /* size of `kinds' list */ const char *const *extensions; /* list of default extensions */ const char *const *patterns; /* list of default file name patterns */ @@ -109,10 +109,15 @@ struct sParserDefinition { simpleParser parser; /* simple parser (common case) */ rescanParser parser2; /* rescanning parser (unusual case) */ selectLanguage* selectLanguage; /* may be used to resolve conflicts */ - unsigned int method; /* See METHOD_ definitions above */ - unsigned int useCork; /* bit fields of corkUsage */ + unsigned int method; /* see METHOD_ definitions above */ + unsigned int useCork; /* bit fields of corkUsage */ bool useMemoryStreamInput; - bool allowNullTag; + bool allowNullTag; /* allow the parser emit tags with empty + strings. If you want to emit a few + specified tags with empty strings, + you don't need this parser-global + allowNullTag; set tagEntryInfo::allowNullTag + instead. */ bool requestAutomaticFQTag; tagRegexTable *tagRegexTable; unsigned int tagRegexCount; diff --git a/main/read.c b/main/read.c index 67586051db..652af8bfe8 100644 --- a/main/read.c +++ b/main/read.c @@ -261,11 +261,6 @@ extern unsigned int countInputLanguageRoles (int kindIndex) return countLanguageRoles (getInputLanguage (), kindIndex); } -extern bool doesInputLanguageAllowNullTag (void) -{ - return doesLanguageAllowNullTag (getInputLanguage ()); -} - extern bool doesInputLanguageRequestAutomaticFQTag (const tagEntryInfo *e) { return doesLanguageRequestAutomaticFQTag (e->langType); diff --git a/main/read_p.h b/main/read_p.h index 985f6ad7d0..4f92b7de28 100644 --- a/main/read_p.h +++ b/main/read_p.h @@ -39,7 +39,6 @@ extern long getInputFileOffsetForLine (unsigned int line); extern unsigned int countInputLanguageKinds (void); extern unsigned int countInputLanguageRoles (int kindIndex); -extern bool doesInputLanguageAllowNullTag (void); extern bool doesInputLanguageRequestAutomaticFQTag (const tagEntryInfo *e); extern bool doesParserRunAsGuest (void); extern bool doesSubparserRun (void); diff --git a/main/writer-ctags.c b/main/writer-ctags.c index 43bcaf768a..6ea99eba25 100644 --- a/main/writer-ctags.c +++ b/main/writer-ctags.c @@ -47,6 +47,7 @@ struct rejection { }; tagWriter uCtagsWriter = { + .oformat = "u-ctags", .writeEntry = writeCtagsEntry, .writePtagEntry = writeCtagsPtagEntry, .printPtagByDefault = true, @@ -55,6 +56,7 @@ tagWriter uCtagsWriter = { .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, .checkOptions = checkCtagsOptions, + .canPrintNullTag = false, #ifdef _WIN32 .overrideFilenameSeparator = overrideFilenameSeparator, #endif @@ -88,6 +90,7 @@ static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentS #endif tagWriter eCtagsWriter = { + .oformat = "e-ctags", .writeEntry = writeCtagsEntry, .writePtagEntry = writeCtagsPtagEntry, .printPtagByDefault = true, @@ -95,8 +98,9 @@ tagWriter eCtagsWriter = { .postWriteEntry = endECTagsFile, .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, - .defaultFileName = CTAGS_FILE, .checkOptions = checkCtagsOptions, + .canPrintNullTag = false, + .defaultFileName = CTAGS_FILE, }; static bool hasTagEntryTabOrNewlineChar (const tagEntryInfo * const tag) diff --git a/main/writer-etags.c b/main/writer-etags.c index 9887a7ac19..5da7926d86 100644 --- a/main/writer-etags.c +++ b/main/writer-etags.c @@ -35,6 +35,7 @@ static bool endEtagsFile (tagWriter *writer, MIO * mio, const char* filename, void *clientData CTAGS_ATTR_UNUSED); tagWriter etagsWriter = { + .oformat = "etags", .writeEntry = writeEtagsEntry, .writePtagEntry = NULL, .preWriteEntry = beginEtagsFile, diff --git a/main/writer-json.c b/main/writer-json.c index fe5280dfbe..713065427c 100644 --- a/main/writer-json.c +++ b/main/writer-json.c @@ -55,6 +55,7 @@ static int writeJsonPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, void *clientData); tagWriter jsonWriter = { + .oformat = "json", .writeEntry = writeJsonEntry, .writePtagEntry = writeJsonPtagEntry, .printPtagByDefault = true, @@ -62,6 +63,7 @@ tagWriter jsonWriter = { .postWriteEntry = NULL, .rescanFailedEntry = NULL, .treatFieldAsFixed = NULL, + .canPrintNullTag = true, .defaultFileName = NULL, }; @@ -308,11 +310,13 @@ extern bool ptagMakeJsonOutputVersion (ptagDesc *desc, langType language CTAGS_A #else /* HAVE_JANSSON */ tagWriter jsonWriter = { + .oformat = "json", .writeEntry = NULL, .writePtagEntry = NULL, .preWriteEntry = NULL, .postWriteEntry = NULL, .defaultFileName = "-", + .canPrintNullTag = false, }; extern bool ptagMakeJsonOutputVersion (ptagDesc *desc, langType language CTAGS_ATTR_UNUSED, diff --git a/main/writer-xref.c b/main/writer-xref.c index 7885e22f8d..a5bf4c47ef 100644 --- a/main/writer-xref.c +++ b/main/writer-xref.c @@ -30,6 +30,7 @@ static int writeXrefPtagEntry (tagWriter *writer, MIO * mio, const ptagDesc *des void *clientData); tagWriter xrefWriter = { + .oformat = "xref", .writeEntry = writeXrefEntry, .writePtagEntry = writeXrefPtagEntry, .printPtagByDefault = false, @@ -37,6 +38,7 @@ tagWriter xrefWriter = { .postWriteEntry = NULL, .rescanFailedEntry = NULL, .treatFieldAsFixed = NULL, + .canPrintNullTag = true, .defaultFileName = NULL, }; diff --git a/main/writer.c b/main/writer.c index f109682bea..9510f633e2 100644 --- a/main/writer.c +++ b/main/writer.c @@ -9,11 +9,14 @@ #include "general.h" +#include "colprint_p.h" #include "debug.h" #include "entry_p.h" #include "options_p.h" #include "writer_p.h" +#include + extern tagWriter uCtagsWriter; extern tagWriter eCtagsWriter; extern tagWriter etagsWriter; @@ -117,6 +120,10 @@ extern bool writerCanPrintPtag (void) return (writer->writePtagEntry)? true: false; } +extern bool writerCanPrintNullTag (void) +{ + return writer->canPrintNullTag; +} extern bool writerDoesTreatFieldAsFixed (int fieldType) { if (writer->treatFieldAsFixed) @@ -187,3 +194,63 @@ extern bool writerPrintPtagByDefault (void) { return writer->printPtagByDefault; } + +extern writerType getWrierForOutputFormat (const char *oformat) +{ + for (int i = 0; i < WRITER_CUSTOM; i++) + { + if (writerTable[i]->oformat == NULL) + continue; + + if (strcmp(writerTable[i]->oformat, oformat) == 0) + { + if (writerTable[i]->writeEntry == NULL) + return WRITER_UNAVAILABLE; + else + return i; + } + } + + return WRITER_UNKNOWN; +} + +#define WRITER_COL_OFORMAT 0 +#define WRITER_COL_AVAILABLE 1 +#define WRITER_COL_NULLTAG 2 + +static int writerColprintCompareLines (struct colprintLine *a , struct colprintLine *b) +{ + const char *a_oformat = colprintLineGetColumn (a, WRITER_COL_OFORMAT); + const char *b_oformat = colprintLineGetColumn (b, WRITER_COL_OFORMAT); + + return strcmp(a_oformat, b_oformat); +} + +extern struct colprintTable * writerColprintTableNew (void) +{ + return colprintTableNew ("L:OFORMAT", "R:DEFAULT", "R:AVAILABLE", "R:NULLTAG", NULL); +} + +extern void printOutputFormats (bool withListHeader, bool machinable, FILE *fp) +{ + struct colprintTable * table = writerColprintTableNew (); + + for (int i = 0; i < WRITER_COUNT; i++) + { + if (!writerTable[i]) + continue; + if (!writerTable[i]->oformat) + continue; + + struct colprintLine * line = colprintTableGetNewLine (table); + colprintLineAppendColumnCString (line, writerTable[i]->oformat); + + colprintLineAppendColumnBool (line, i == WRITER_DEFAULT); + colprintLineAppendColumnBool (line, writerTable[i]->writeEntry? true: false); + colprintLineAppendColumnBool (line, writerTable[i]->canPrintNullTag); + } + + colprintTableSort (table, writerColprintCompareLines); + colprintTablePrint (table, 0, withListHeader, machinable, fp); + colprintTableDelete (table); +} diff --git a/main/writer_p.h b/main/writer_p.h index e52aa591cb..0ed872df18 100644 --- a/main/writer_p.h +++ b/main/writer_p.h @@ -20,6 +20,8 @@ preWriteEntry, postWriteEntry should free it. */ typedef enum eWriterType { + WRITER_UNAVAILABLE = -2, /* Defined but no implementation. */ + WRITER_UNKNOWN = -1, WRITER_DEFAULT, WRITER_U_CTAGS = WRITER_DEFAULT, WRITER_E_CTAGS, @@ -33,6 +35,8 @@ typedef enum eWriterType { struct sTagWriter; typedef struct sTagWriter tagWriter; struct sTagWriter { + const char *oformat; /* name used in CLI: --output-format= + * NULL is acceptable.*/ int (* writeEntry) (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag, void *clientData); int (* writePtagEntry) (tagWriter *writer, MIO * mio, const ptagDesc *desc, @@ -54,6 +58,8 @@ struct sTagWriter { void (* checkOptions) (tagWriter *writer, bool fieldsWereReset); + bool canPrintNullTag; + #ifdef _WIN32 enum filenameSepOp (* overrideFilenameSeparator) (enum filenameSepOp currentSetting); #endif /* _WIN32 */ @@ -95,11 +101,15 @@ extern bool ptagMakeCtagsOutputFilesep (ptagDesc *desc, langType language CTAGS_ extern bool ptagMakeCtagsOutputExcmd (ptagDesc *desc, langType language CTAGS_ATTR_UNUSED, const void *data); extern bool writerCanPrintPtag (void); +extern bool writerCanPrintNullTag (void); extern bool writerDoesTreatFieldAsFixed (int fieldType); extern void writerCheckOptions (bool fieldsWereReset); extern bool writerPrintPtagByDefault (void); +extern writerType getWrierForOutputFormat (const char *oformat); +extern void printOutputFormats (bool withListHeader, bool machinable, FILE *fp); + #ifdef _WIN32 extern enum filenameSepOp getFilenameSeparator (enum filenameSepOp currentSetting); #endif /* _WIN32 */ diff --git a/main/xtag.c b/main/xtag.c index 60add848ef..abdc03c19a 100644 --- a/main/xtag.c +++ b/main/xtag.c @@ -77,6 +77,8 @@ static xtagDefinition xtagDefinitions [] = { "Include tags generated by subparsers"}, { true, '\0', "anonymous", "Include tags for non-named objects like lambda"}, + { false, 'z', "nulltag", + "Include tags with empty strings as their names"}, }; static unsigned int xtagObjectUsed; diff --git a/main/xtag.h b/main/xtag.h index 735c2857e6..1d0e794159 100644 --- a/main/xtag.h +++ b/main/xtag.h @@ -34,6 +34,7 @@ typedef enum eXtagType { /* extra tag content control */ XTAG_TAGS_GENERATED_BY_GUEST_PARSERS = XTAG_GUEST, /* Geany uses the old name */ XTAG_SUBPARSER, XTAG_ANONYMOUS, + XTAG_NULLTAG, XTAG_COUNT } xtagType; @@ -42,7 +43,7 @@ struct sXtagDefinition { bool enabled; /* letter, and ftype are initialized in the main part, not in a parser. */ -#define NUL_XTAG_LETTER '\0' +#define NUL_XTAG_LETTER '\0' /* Nothing todo with NULLTAG. */ unsigned char letter; const char* name; /* used in extra: field */ const char* description; /* displayed in --list-extra output */ diff --git a/man/ctags.1.rst.in b/man/ctags.1.rst.in index 788dd2dac9..ff29192592 100644 --- a/man/ctags.1.rst.in +++ b/man/ctags.1.rst.in @@ -318,6 +318,8 @@ Output Format Options the ctags executable is built with ``libjansson``. See ctags-json-output(5) for more about ``json`` format. + See also ``--list-output-formats``. + ``-e`` Same as ``--output-format=etags``. Enable etags mode, which will create a tag file for use with the Emacs @@ -1236,6 +1238,14 @@ Listing Options definition. See ctags-optlib(7). +``--list-output-formats`` + Lists the output formats that can be used in ``--output-format`` option. + + ``NULLTAG`` column represetns whether the format supports *null tags* or + not. See ``nulltag``/``z`` in "`Extras`_" about the null tags. + + (since verison 6.2.0) + ``--list-params[=(|all)]`` Lists the parameters for either the specified ** or ``all`` languages, and then exits. @@ -1835,6 +1845,8 @@ roles of tags to include in the output file for a particular language. Inquire the output of "``@CTAGS_NAME_EXECUTABLE@ --list-roles``" for the list of roles. +.. _extras: + Extras ~~~~~~ @@ -1927,6 +1939,25 @@ The meaning of major extras is as follows (long-name flag/one-letter flag): The etags mode enables the ``Unknown`` parser implicitly. +``nulltag``/``z`` + Include tags (*null tags*) having empty strings as their names. + Generally speaking, trying to make a null tag is a sign of a parser bug + or broken input. @CTAGS_NAME_EXECUTABLE@ warns such trying or throws the + null tag away. To suppress the warnings, use ``--quiet`` option. + + On the other hand, null tags are valid in some languages. + Consider ``{"": val}`` in a JavaScript sourece code. The empty string is + valid as a key. If a parser intentionally makes a null tag (a valid null tag), + @CTAGS_NAME_EXECUTABLE@ doesn't warn but discard it by default. + + The discards are due because some output formats may not consider null tags. + + With ``nulltag``/``z`` extra, you can force ctags to emit the nulltags. This extra + is effective only if the output format supports null tags. ``--list-output-formats`` + option tells you which output formats support null tags. + + (since version 6.2.0) + ``pseudo``/``p`` Include pseudo-tags. Enabled by default unless the tag file is written to standard output. See ctags-client-tools(7) about @@ -2273,6 +2304,8 @@ The official Universal Ctags web site at: https://ctags.io/ Also ``ex(1)``, ``vi(1)``, ``elvis(1)``, or, better yet, ``vim(1)``, the official editor of ctags. For more information on ``vim(1)``, see the Vim web site at: https://www.vim.org/ +About the file format for ``TAGS``, see `emacs git +`_. AUTHOR ------ diff --git a/parsers/jscript.c b/parsers/jscript.c index 8e64a02758..bf62ea545c 100644 --- a/parsers/jscript.c +++ b/parsers/jscript.c @@ -480,7 +480,7 @@ static int makeJsRefTagsForNameChain (char *name_chain, const tokenInfo *token, static int makeJsTagCommon (const tokenInfo *const token, const jsKind kind, vString *const signature, vString *const inheritance, - bool anonymous) + bool anonymous, bool nulltag) { int index = CORK_NIL; const char *name = vStringValue (token->string); @@ -550,6 +550,9 @@ static int makeJsTagCommon (const tokenInfo *const token, const jsKind kind, if (anonymous) markTagExtraBit (&e, XTAG_ANONYMOUS); + if (nulltag) + e.allowNullTag = 1; + index = makeTagEntry (&e); /* We shold remove This condition. We should fix the callers passing * an empty name instead. makeTagEntry() returns CORK_NIL if the tag @@ -563,13 +566,19 @@ static int makeJsTagCommon (const tokenInfo *const token, const jsKind kind, static int makeJsTag (const tokenInfo *const token, const jsKind kind, vString *const signature, vString *const inheritance) { - return makeJsTagCommon (token, kind, signature, inheritance, false); + return makeJsTagCommon (token, kind, signature, inheritance, false, false); +} + +static int makeJsNullTag (const tokenInfo *const token, const jsKind kind, + vString *const signature, vString *const inheritance) +{ + return makeJsTagCommon (token, kind, signature, inheritance, false, true); } static int makeClassTagCommon (tokenInfo *const token, vString *const signature, vString *const inheritance, bool anonymous) { - return makeJsTagCommon (token, JSTAG_CLASS, signature, inheritance, anonymous); + return makeJsTagCommon (token, JSTAG_CLASS, signature, inheritance, anonymous, false); } static int makeClassTag (tokenInfo *const token, vString *const signature, @@ -582,7 +591,7 @@ static int makeFunctionTagCommon (tokenInfo *const token, vString *const signatu bool generator, bool anonymous) { return makeJsTagCommon (token, generator ? JSTAG_GENERATOR : JSTAG_FUNCTION, signature, NULL, - anonymous); + anonymous, false); } static int makeFunctionTag (tokenInfo *const token, vString *const signature, bool generator) @@ -1430,7 +1439,7 @@ static int parseMethodsInAnonymousObject (tokenInfo *const token) anonGenerate (anon_object->string, "anonymousObject", JSTAG_VARIABLE); anon_object->type = TOKEN_IDENTIFIER; - index = makeJsTagCommon (anon_object, JSTAG_VARIABLE, NULL, NULL, true); + index = makeJsTagCommon (anon_object, JSTAG_VARIABLE, NULL, NULL, true, false); if (! parseMethods (token, index, false)) { /* If no method is found, the anonymous object @@ -2257,7 +2266,12 @@ static bool parseMethods (tokenInfo *const token, int class_index, deleteToken (saved_token); has_methods = true; - index_for_name = makeJsTag (name, JSTAG_PROPERTY, NULL, NULL); + /* property names can be empty strings such as { "": true }, + * use a special function for generating tags for those */ + if (name->string->length == 0) + index_for_name = makeJsNullTag (name, JSTAG_PROPERTY, NULL, NULL); + else + index_for_name = makeJsTag (name, JSTAG_PROPERTY, NULL, NULL); if (p != CORK_NIL) moveChildren (p, index_for_name); } @@ -2341,7 +2355,7 @@ static bool parseES6Class (tokenInfo *const token, const tokenInfo *target_name) TRACE_PRINT("Emitting tag for class '%s'", vStringValue(target_name->string)); int r = makeJsTagCommon (target_name, JSTAG_CLASS, NULL, inheritance, - (is_anonymous && (target_name == class_name))); + (is_anonymous && (target_name == class_name)), false); if (! is_anonymous && target_name != class_name) { @@ -2675,7 +2689,7 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st if ( parseMethods(token, p, false) ) { jsKind kind = state->foundThis || strchr (vStringValue(name->string), '.') != NULL ? JSTAG_PROPERTY : JSTAG_VARIABLE; - state->indexForName = makeJsTagCommon (name, kind, NULL, NULL, anon_object); + state->indexForName = makeJsTagCommon (name, kind, NULL, NULL, anon_object, false); moveChildren (p, state->indexForName); } else if ( token->nestLevel == 0 && state->isGlobal ) @@ -2722,7 +2736,7 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st */ if ( ( token->nestLevel == 0 && state->isGlobal ) || kind == JSTAG_PROPERTY ) { - state->indexForName = makeJsTagCommon (name, kind, NULL, NULL, false); + state->indexForName = makeJsTagCommon (name, kind, NULL, NULL, false, false); } } else if (isKeyword (token, KEYWORD_new)) diff --git a/parsers/pascal.c b/parsers/pascal.c index e3995de201..ffae2e8cf9 100644 --- a/parsers/pascal.c +++ b/parsers/pascal.c @@ -59,7 +59,7 @@ static void createPascalTag ( initTagEntry (tag, NULL, KIND_GHOST_INDEX); } -static void makePascalTag (const tagEntryInfo* const tag) +static void makePascalTag (tagEntryInfo* const tag) { if (tag->name != NULL) makeTagEntry (tag);