From 0d238797e44d6296047491ab671cbd2c35881200 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 8 Dec 2024 18:14:09 +0900 Subject: [PATCH] main: add nulltag/0, 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, and The --extras=+0 (or --extras=+{nulltag}) option is specified. TODO: - versioning - updating ctags(1), - updating the hacking guide - make readtags warn "unsupported" if it finds "!_TAG_FIELD_DESCRIPTION" 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 +++++++++++ main/entry.c | 18 ++++++++++- main/parse.c | 14 +++++++++ main/writer-ctags.c | 1 + main/writer-json.c | 1 + main/writer.c | 4 +++ main/writer_p.h | 3 ++ main/xtag.c | 2 ++ main/xtag.h | 3 +- 16 files changed, 107 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..3bc775aba0 100644 --- a/Tmain/extras-long.d/stdout-expected.txt +++ b/Tmain/extras-long.d/stdout-expected.txt @@ -1,5 +1,6 @@ # resetting - anonymous no NONE no Include tags for non-named objects like lambda +0 nulltag no NONE no Include tags with empty strings as their names F fileScope no NONE no Include tags of file scope f inputFile no NONE no Include an entry for the base file name of every input file g guest no NONE no Include tags generated by guest parsers @@ -18,6 +19,7 @@ s subparser yes NONE no Include tags generated by - whitespaceSwapped yes Robot no Include tags swapping whitespace and underscore chars # enabling 1 - anonymous yes NONE no Include tags for non-named objects like lambda +0 nulltag no NONE no Include tags with empty strings as their names F fileScope yes NONE no Include tags of file scope f inputFile no NONE no Include an entry for the base file name of every input file g guest no NONE no Include tags generated by guest parsers @@ -36,6 +38,7 @@ s subparser yes NONE no Include tags generated by - whitespaceSwapped yes Robot no Include tags swapping whitespace and underscore chars # disabling 1 - anonymous yes NONE no Include tags for non-named objects like lambda +0 nulltag no NONE no Include tags with empty strings as their names F fileScope no NONE no Include tags of file scope f inputFile no NONE no Include an entry for the base file name of every input file g guest no NONE no Include tags generated by guest parsers @@ -54,6 +57,7 @@ s subparser yes NONE no Include tags generated by - whitespaceSwapped yes Robot no Include tags swapping whitespace and underscore chars # combination - anonymous yes NONE no Include tags for non-named objects like lambda +0 nulltag no NONE no Include tags with empty strings as their names F fileScope no NONE no Include tags of file scope f inputFile yes NONE no Include an entry for the base file name of every input file g guest no NONE no Include tags generated by guest parsers @@ -72,6 +76,7 @@ s subparser yes NONE no Include tags generated by - whitespaceSwapped yes Robot no Include tags swapping whitespace and underscore chars # combination with letters - anonymous yes NONE no Include tags for non-named objects like lambda +0 nulltag no NONE no Include tags with empty strings as their names F fileScope no NONE no Include tags of file scope f inputFile no NONE no Include an entry for the base file name of every input file g guest no NONE no Include tags generated by guest parsers 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..a9a3ca4082 100644 --- a/Tmain/list-extras.d/stdout-expected.txt +++ b/Tmain/list-extras.d/stdout-expected.txt @@ -1,5 +1,6 @@ #LETTER NAME ENABLED LANGUAGE FIXED DESCRIPTION - anonymous yes NONE no Include tags for non-named objects like lambda +0 nulltag yes NONE no Include tags with empty strings as their names F fileScope yes NONE no Include tags of file scope f inputFile yes NONE no Include an entry for the base file name of every input file g guest yes NONE no Include tags generated by guest parsers @@ -18,6 +19,7 @@ s subparser yes NONE no Include tags generated by - whitespaceSwapped yes Robot no Include tags swapping whitespace and underscore chars #LETTER NAME ENABLED LANGUAGE FIXED DESCRIPTION - anonymous yes NONE no Include tags for non-named objects like lambda +0 nulltag yes NONE no Include tags with empty strings as their names F fileScope yes NONE no Include tags of file scope f inputFile yes NONE no Include an entry for the base file name of every input file g guest yes NONE no Include tags generated by guest parsers @@ -36,6 +38,7 @@ s subparser yes NONE no Include tags generated by subparsers - whitespaceSwapped yes Robot no Include tags swapping whitespace and underscore chars #LETTER NAME ENABLED LANGUAGE FIXED DESCRIPTION - anonymous no NONE no Include tags for non-named objects like lambda +0 nulltag no NONE no Include tags with empty strings as their names F fileScope no NONE no Include tags of file scope f inputFile no NONE no Include an entry for the base file name of every input file g guest no NONE no Include tags generated by guest parsers 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..566b654768 --- /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 u-ctags; 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=-0 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=+0 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..5a5bc0480e --- /dev/null +++ b/Tmain/nulltag-extra.d/stdout-expected.txt @@ -0,0 +1,17 @@ +# no extra (u-ctags) +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 (u-ctags) +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 (u-ctags) +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) + input.cst /^z$/;" z +# with --extras=+{nulltag}' (u-ctags) +ctags: Notice: No options will be read from files or environment +ctags: Notice: ignoring null tag in input.cst(line: 1, language: CTagsSelfTest) + input.cst /^z$/;" z 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 43bcaf768a..d997e1a7bd 100644 --- a/main/writer-ctags.c +++ b/main/writer-ctags.c @@ -55,6 +55,7 @@ tagWriter uCtagsWriter = { .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, .checkOptions = checkCtagsOptions, + .canPrintNullTag = true, #ifdef _WIN32 .overrideFilenameSeparator = overrideFilenameSeparator, #endif diff --git a/main/writer-json.c b/main/writer-json.c index fe5280dfbe..cf3b0c106c 100644 --- a/main/writer-json.c +++ b/main/writer-json.c @@ -313,6 +313,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.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..14c72a2386 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, '0', "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 */