From 0577e1975d8bc87a4aff478c26eb80b3a83a9e16 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 15 Dec 2024 03:04:43 +0900 Subject: [PATCH 01/12] docs(web): remove output-json.rst We have the man page for the format already. Signed-off-by: Masatake YAMATO --- docs/output-format.rst | 3 +-- docs/output-json.rst | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 docs/output-json.rst diff --git a/docs/output-format.rst b/docs/output-format.rst index d21181e249..556c8dbfdf 100644 --- a/docs/output-format.rst +++ b/docs/output-format.rst @@ -38,7 +38,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 +47,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) `. From e3aad58a6309367948f2a45802c9597096ec85a5 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 15 Dec 2024 03:13:29 +0900 Subject: [PATCH 02/12] docs: move the reference of TAGS format from the web page to SEE ALSO section of ctags(1). Signed-off-by: Masatake YAMATO --- docs/man/ctags.1.rst | 2 ++ docs/output-format.rst | 5 +---- man/ctags.1.rst.in | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/man/ctags.1.rst b/docs/man/ctags.1.rst index 9d70ff8953..e5f8f65d7b 100644 --- a/docs/man/ctags.1.rst +++ b/docs/man/ctags.1.rst @@ -2273,6 +2273,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/output-format.rst b/docs/output-format.rst index 556c8dbfdf..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. diff --git a/man/ctags.1.rst.in b/man/ctags.1.rst.in index 788dd2dac9..ca317d5c1f 100644 --- a/man/ctags.1.rst.in +++ b/man/ctags.1.rst.in @@ -2273,6 +2273,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 ------ From e8a68293ea532bf708799dc1c1a4a7c63e4f6846 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 8 Dec 2024 17:37:27 +0900 Subject: [PATCH 03/12] main,comment: adjust the offsets and capitalization Signed-off-by: Masatake YAMATO --- main/parse.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/parse.h b/main/parse.h index 6f3051b619..451bf0937f 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,8 +109,8 @@ 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 requestAutomaticFQTag; From 1aea3e8287877904282aa120ae07dce108c6a61d Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sat, 14 Dec 2024 18:19:58 +0900 Subject: [PATCH 04/12] main: (bug fix) allow to use "R:" as the last column The original code assumed using "L:" as the last column. This change fixes the wrong assumption. Signed-off-by: Masatake YAMATO --- main/colprint.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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); From 2e77ded100b4cae55ea5abcf9305408f72efe392 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Thu, 19 Dec 2024 19:30:44 +0900 Subject: [PATCH 05/12] main,cosmetic: make the order of the members in eCtagsWriter as the same as that of tagWriter definition --- main/writer-ctags.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/writer-ctags.c b/main/writer-ctags.c index 43bcaf768a..19f7c32030 100644 --- a/main/writer-ctags.c +++ b/main/writer-ctags.c @@ -95,8 +95,8 @@ tagWriter eCtagsWriter = { .postWriteEntry = endECTagsFile, .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, - .defaultFileName = CTAGS_FILE, .checkOptions = checkCtagsOptions, + .defaultFileName = CTAGS_FILE, }; static bool hasTagEntryTabOrNewlineChar (const tagEntryInfo * const tag) From bded29d98449eb7cd4e2aa1caf98fafe199628ab Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 8 Dec 2024 16:50:40 +0900 Subject: [PATCH 06/12] main: remove 'const' modifier from makeTagEntry This is preparation for "null tag support". Signed-off-by: Masatake YAMATO --- main/entry.c | 10 +++++----- main/entry.h | 2 +- parsers/pascal.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/main/entry.c b/main/entry.c index 6795c1b824..b6c2f18245 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; @@ -1773,8 +1773,8 @@ static void writeTagEntry (const tagEntryInfo *const tag) #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 +1791,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 +1926,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); diff --git a/main/entry.h b/main/entry.h index 19ac0865da..22b141fd7d 100644 --- a/main/entry.h +++ b/main/entry.h @@ -164,7 +164,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/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); From fed6414aafefc578aa34326202b081fe083922fc Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Wed, 4 Dec 2024 22:05:10 +0900 Subject: [PATCH 07/12] main: add allowNullTag flag to tagEntryInfo We have allowNullTag in parserDefinition already. However, we may lose the chance of signs of bugs if we turn parserDefinition::allowNullTag on. With tagEntryInfo:allowNullTag, we can write a null tag without losing the chance. Signed-off-by: Masatake YAMATO --- main/entry.c | 4 +++- main/entry.h | 7 +++++++ main/parse.h | 7 ++++++- main/read.c | 5 ----- main/read_p.h | 1 - 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/main/entry.c b/main/entry.c index b6c2f18245..951e31bfe3 100644 --- a/main/entry.c +++ b/main/entry.c @@ -1938,7 +1938,7 @@ extern int makeTagEntry (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)); @@ -2082,6 +2082,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 22b141fd7d..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. */ diff --git a/main/parse.h b/main/parse.h index 451bf0937f..bd67c9621a 100644 --- a/main/parse.h +++ b/main/parse.h @@ -112,7 +112,12 @@ struct sParserDefinition { 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); From bddcc32ec81f944586a742762ae1cc5e429f3dba Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 8 Dec 2024 18:14:09 +0900 Subject: [PATCH 08/12] main: add nulltag/z, a new extra Close #4151. Consider a parser attempting to emit a null tag, a tag whose name is the empty string '\0'. Original Behavior: It warns "ignoring null tag..." if both parserDefinition::allowNullTag and tagEntryInfo::allowNullTag are unset. It does not warn if either parserDefinition::allowNullTag or tagEntryInfo::allowNullTag is set. It does not emit the null tag, even if allowNullTag is set. With This Change: The code now emits the null tag if: Either parserDefinition::allowNullTag or tagEntryInfo::allowNullTag is set, The --extras=+z (or --extras=+{nulltag}) option is specified, and The output format supports to emit null tags. The xref and json output formats support to emit null tags. Signed-off-by: Masatake YAMATO --- Tmain/extras-long.d/stdout-expected.txt | 5 ++++ .../json-output-format.d/stdout-expected.txt | 1 + .../kind-abnormal-spec.d/stdout-expected.txt | 4 +++ Tmain/list-extras.d/stdout-expected.txt | 3 ++ Tmain/nulltag-extra.d/input.cst | 3 ++ Tmain/nulltag-extra.d/run.sh | 30 +++++++++++++++++++ Tmain/nulltag-extra.d/stderr-expected.txt | 0 Tmain/nulltag-extra.d/stdout-expected.txt | 17 +++++++++++ docs/man/ctags.1.rst | 20 +++++++++++++ docs/news/HEAD.rst | 10 +++++++ main/entry.c | 18 ++++++++++- main/parse.c | 14 +++++++++ main/writer-ctags.c | 2 ++ main/writer-json.c | 2 ++ main/writer-xref.c | 1 + main/writer.c | 4 +++ main/writer_p.h | 3 ++ main/xtag.c | 2 ++ main/xtag.h | 3 +- man/ctags.1.rst.in | 20 +++++++++++++ 20 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 Tmain/nulltag-extra.d/input.cst create mode 100644 Tmain/nulltag-extra.d/run.sh create mode 100644 Tmain/nulltag-extra.d/stderr-expected.txt create mode 100644 Tmain/nulltag-extra.d/stdout-expected.txt 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/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/docs/man/ctags.1.rst b/docs/man/ctags.1.rst index e5f8f65d7b..106a54ff35 100644 --- a/docs/man/ctags.1.rst +++ b/docs/man/ctags.1.rst @@ -1835,6 +1835,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 +1929,24 @@ 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. + + (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 diff --git a/docs/news/HEAD.rst b/docs/news/HEAD.rst index 35b8034358..956d97a6c7 100644 --- a/docs/news/HEAD.rst +++ b/docs/news/HEAD.rst @@ -5,6 +5,16 @@ Changes in 6.?.0 New and extended options and their flags --------------------------------------------------------------------- +``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/main/entry.c b/main/entry.c index 951e31bfe3..89713624e2 100644 --- a/main/entry.c +++ b/main/entry.c @@ -1770,6 +1770,15 @@ static void writeTagEntry (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) { @@ -1939,10 +1948,17 @@ extern int makeTagEntry (tagEntryInfo *const tag) if (tag->name [0] == '\0' && (!tag->placeholder)) { 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) 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/writer-ctags.c b/main/writer-ctags.c index 19f7c32030..8136100895 100644 --- a/main/writer-ctags.c +++ b/main/writer-ctags.c @@ -55,6 +55,7 @@ tagWriter uCtagsWriter = { .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, .checkOptions = checkCtagsOptions, + .canPrintNullTag = false, #ifdef _WIN32 .overrideFilenameSeparator = overrideFilenameSeparator, #endif @@ -96,6 +97,7 @@ tagWriter eCtagsWriter = { .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, .checkOptions = checkCtagsOptions, + .canPrintNullTag = false, .defaultFileName = CTAGS_FILE, }; diff --git a/main/writer-json.c b/main/writer-json.c index fe5280dfbe..2f66f088ae 100644 --- a/main/writer-json.c +++ b/main/writer-json.c @@ -62,6 +62,7 @@ tagWriter jsonWriter = { .postWriteEntry = NULL, .rescanFailedEntry = NULL, .treatFieldAsFixed = NULL, + .canPrintNullTag = true, .defaultFileName = NULL, }; @@ -313,6 +314,7 @@ tagWriter jsonWriter = { .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..048f121c43 100644 --- a/main/writer-xref.c +++ b/main/writer-xref.c @@ -37,6 +37,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..cfe324dbcc 100644 --- a/main/writer.c +++ b/main/writer.c @@ -117,6 +117,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) diff --git a/main/writer_p.h b/main/writer_p.h index e52aa591cb..59ba92fa20 100644 --- a/main/writer_p.h +++ b/main/writer_p.h @@ -54,6 +54,8 @@ struct sTagWriter { void (* checkOptions) (tagWriter *writer, bool fieldsWereReset); + bool canPrintNullTag; + #ifdef _WIN32 enum filenameSepOp (* overrideFilenameSeparator) (enum filenameSepOp currentSetting); #endif /* _WIN32 */ @@ -95,6 +97,7 @@ 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); 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 ca317d5c1f..3aa0eff60e 100644 --- a/man/ctags.1.rst.in +++ b/man/ctags.1.rst.in @@ -1835,6 +1835,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 +1929,24 @@ 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. + + (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 From 3ef783254d8c5a7e0f405c57e2ec889d4ffebdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 8 Dec 2024 18:19:15 +0100 Subject: [PATCH 09/12] JavaScript: generate tags for empty properties with setting allowNullTag In javascript it is legal to have e.g. {"": false}. With the original code the parser attempts to generate the tag for the empty string which produces warning. With setting allowNullTag member of tagEntryInfo, the parser can generate such tags without producing warnings. Actual tag emission is controlled by --extras=+{nulltag}. This commit is based on the commit in #4153. Co-Author: Masatake YAMATO --- .../args.ctags | 4 +++ .../expected.tags-json | 2 ++ .../features | 1 + .../input.js | 1 + .../js-extract-empty-property.d/args.ctags | 5 +++ .../expected.tags-x | 2 ++ .../js-extract-empty-property.d/input.js | 1 + .../js-skip-empty-property.d/args.ctags | 1 + .../js-skip-empty-property.d/expected.tags | 1 + .../js-skip-empty-property.d/input.js | 1 + parsers/jscript.c | 32 +++++++++++++------ 11 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 Units/parser-javascript.r/js-extract-empty-property-in-json.d/args.ctags create mode 100644 Units/parser-javascript.r/js-extract-empty-property-in-json.d/expected.tags-json create mode 100644 Units/parser-javascript.r/js-extract-empty-property-in-json.d/features create mode 100644 Units/parser-javascript.r/js-extract-empty-property-in-json.d/input.js create mode 100644 Units/parser-javascript.r/js-extract-empty-property.d/args.ctags create mode 100644 Units/parser-javascript.r/js-extract-empty-property.d/expected.tags-x create mode 100644 Units/parser-javascript.r/js-extract-empty-property.d/input.js create mode 100644 Units/parser-javascript.r/js-skip-empty-property.d/args.ctags create mode 100644 Units/parser-javascript.r/js-skip-empty-property.d/expected.tags create mode 100644 Units/parser-javascript.r/js-skip-empty-property.d/input.js 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/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)) From eb92818503e0e8850bd4af6676f6bf45392442ca Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sat, 14 Dec 2024 12:30:49 +0900 Subject: [PATCH 10/12] main,refactor: make name of output format a property of writer In the original code, the names output of formats are defined in main/options.c; processOutputFormat maps a name output of a format to a writer. This is a preparation for implementing --list-output-format option. --- main/options.c | 40 +++++++++++++++++++++++++++++++--------- main/writer-ctags.c | 2 ++ main/writer-etags.c | 1 + main/writer-json.c | 2 ++ main/writer-xref.c | 1 + main/writer.c | 21 +++++++++++++++++++++ main/writer_p.h | 6 ++++++ 7 files changed, 64 insertions(+), 9 deletions(-) diff --git a/main/options.c b/main/options.c index 7888250dd7..8bb816d9da 100644 --- a/main/options.c +++ b/main/options.c @@ -2406,20 +2406,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, diff --git a/main/writer-ctags.c b/main/writer-ctags.c index 8136100895..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, @@ -89,6 +90,7 @@ static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentS #endif tagWriter eCtagsWriter = { + .oformat = "e-ctags", .writeEntry = writeCtagsEntry, .writePtagEntry = writeCtagsPtagEntry, .printPtagByDefault = true, 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 2f66f088ae..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, @@ -309,6 +310,7 @@ extern bool ptagMakeJsonOutputVersion (ptagDesc *desc, langType language CTAGS_A #else /* HAVE_JANSSON */ tagWriter jsonWriter = { + .oformat = "json", .writeEntry = NULL, .writePtagEntry = NULL, .preWriteEntry = NULL, diff --git a/main/writer-xref.c b/main/writer-xref.c index 048f121c43..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, diff --git a/main/writer.c b/main/writer.c index cfe324dbcc..bf2c7dfd6d 100644 --- a/main/writer.c +++ b/main/writer.c @@ -14,6 +14,8 @@ #include "options_p.h" #include "writer_p.h" +#include + extern tagWriter uCtagsWriter; extern tagWriter eCtagsWriter; extern tagWriter etagsWriter; @@ -191,3 +193,22 @@ 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; +} diff --git a/main/writer_p.h b/main/writer_p.h index 59ba92fa20..18a86bd446 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, @@ -103,6 +107,8 @@ extern bool writerDoesTreatFieldAsFixed (int fieldType); extern void writerCheckOptions (bool fieldsWereReset); extern bool writerPrintPtagByDefault (void); +extern writerType getWrierForOutputFormat (const char *oformat); + #ifdef _WIN32 extern enum filenameSepOp getFilenameSeparator (enum filenameSepOp currentSetting); #endif /* _WIN32 */ From bbb8fd2aa93555b19f809820e31483a0cbd18aeb Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sat, 14 Dec 2024 18:26:39 +0900 Subject: [PATCH 11/12] main: add --list-output-formats option Signed-off-by: Masatake YAMATO --- Tmain/list-output-formats.d/exit-expected.txt | 1 + Tmain/list-output-formats.d/run.sh | 7 ++++ .../list-output-formats.d/stderr-expected.txt | 0 .../list-output-formats.d/stdout-expected.txt | 5 +++ docs/man/ctags.1.rst | 13 +++++- docs/news/HEAD.rst | 4 ++ main/options.c | 10 +++++ main/writer.c | 42 +++++++++++++++++++ main/writer_p.h | 1 + man/ctags.1.rst.in | 13 +++++- 10 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 Tmain/list-output-formats.d/exit-expected.txt create mode 100755 Tmain/list-output-formats.d/run.sh create mode 100644 Tmain/list-output-formats.d/stderr-expected.txt create mode 100644 Tmain/list-output-formats.d/stdout-expected.txt 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/docs/man/ctags.1.rst b/docs/man/ctags.1.rst index 106a54ff35..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. @@ -1943,7 +1953,8 @@ The meaning of major extras is as follows (long-name flag/one-letter flag): 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. + 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) diff --git a/docs/news/HEAD.rst b/docs/news/HEAD.rst index 956d97a6c7..c209e7556e 100644 --- a/docs/news/HEAD.rst +++ b/docs/news/HEAD.rst @@ -5,6 +5,10 @@ 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. diff --git a/main/options.c b/main/options.c index 8bb816d9da..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); @@ -2899,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/writer.c b/main/writer.c index bf2c7dfd6d..9510f633e2 100644 --- a/main/writer.c +++ b/main/writer.c @@ -9,6 +9,7 @@ #include "general.h" +#include "colprint_p.h" #include "debug.h" #include "entry_p.h" #include "options_p.h" @@ -212,3 +213,44 @@ extern writerType getWrierForOutputFormat (const char *oformat) 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 18a86bd446..0ed872df18 100644 --- a/main/writer_p.h +++ b/main/writer_p.h @@ -108,6 +108,7 @@ 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); diff --git a/man/ctags.1.rst.in b/man/ctags.1.rst.in index 3aa0eff60e..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. @@ -1943,7 +1953,8 @@ The meaning of major extras is as follows (long-name flag/one-letter flag): 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. + 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) From a82df3637f78f72338f4d81899e46987d0637ba6 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 15 Dec 2024 03:40:06 +0900 Subject: [PATCH 12/12] docs(web): write about null tags for parser developers Signed-off-by: Masatake YAMATO --- docs/internal.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~