mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
AsciiDoc (.adoc, .asciidoc) is now indexed, without a grammar like PDF: a
Section per heading (`=` .. `======`, or `#`), the text up to the next
heading as its docstring, delimited blocks respected (listing, literal,
comment, passthrough, example, sidebar, quote, table, fences). Its
references become MENTIONS edges, via "asciidoc", from the section they are
written in:
include include::target[...] -- Asciidoctor's preprocessor directive,
read in every block but a comment block; the page's own
attributes, then the Antora component's and the playbook's,
substituted
attribute an {attribute} whose value is an Antora javadoc link
({javadoc-root}/<module>/<package path>/<Type>.html[#member])
binds the Java or Kotlin type, or its one member, that the
repository's main sources declare under that path
code_path, monospace spans naming a path or a qualified name (the
code_name Markdown code-span rules)
The rules are the field test's (H8 Asciidoctor/Antora harvester, audited
~100 % precise): an Antora resource ID ([component:][module:]family$path)
names <component>/modules/<module>/<family dir>/<path>, or the file a
collector scan contributes there (a scan of build output is generated: no
row); any other target is relative to the including file. `lines=` and
`tag=`/`tags=` regions bind by H8's rule: edge blank and comment lines
trimmed, the innermost enclosing definition, unless that is a type (or there
is none) and the definitions inside cover every other line (an annotation
right above one counts) -- then that definition; tag regions come from the
included file's own `tag::x[]` / `end::x[]` comments, read from that indexed
file; a tag the file lacks binds the file. One reference makes one edge: a
region holding several top-level definitions binds the file.
Antora settings travel as YAML scope blobs (antora.yml: component name,
asciidoc attributes, collector scans; antora-playbook*.yml: attributes),
read as text, so an incremental run resolves as a full one; a change to them
is GLOBAL. AsciiDoc joins the documents bound by lines: a body edit of an
included file re-resolves its pages.
Also: the Markdown resolver's module index no longer takes AsciiDoc or PDF
files for code modules.
Proof on junit-team/junit-framework at 6c8ca116 (H8's corpus and commit),
C against H8's recorded links: javadoc attributes 362/362 identical;
includes 292 identical, 60 bind the type enclosing the several definitions
H8 names, 7 regions with several top-level definitions bind the file. 317
sections, 668 edges, no include rows (9 generated partials, 1 file type the
indexer does not read); monospace spans 30/30 correct by hand. 8.3 s for the
whole index.
Tests: suite doc_links_adoc (structure and tokens, the Antora blob, the
pipeline: resource IDs through a collector scan, tag regions, a region with
its imports binding its class, lines=, javadoc attributes, spans; incremental
== full: a moved tag region, a changed component attribute). Revert check:
without H8's region rule and without AsciiDoc among the line-bound documents
exactly their assertions fail (observed).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
288 lines
13 KiB
C
288 lines
13 KiB
C
/*
|
|
* test_doc_links_adoc.c — AsciiDoc documents -> Sections and MENTIONS edges
|
|
* (doc_adoc.c, doc_links_adoc.c).
|
|
*
|
|
* Headings and delimited blocks, the tokens (include with the page's
|
|
* attributes, attribute references, monospace spans), the Antora scope blob,
|
|
* and the resolver: resource IDs and collector scans, tag regions read from
|
|
* the included file, lines, javadoc attributes to types and members, the
|
|
* monospace spans through the Markdown rules, incremental == full.
|
|
*/
|
|
#include "../src/foundation/compat.h"
|
|
#include "test_framework.h"
|
|
#include "test_helpers.h"
|
|
#include "test_doc_mentions_helpers.h"
|
|
|
|
#include "cbm.h"
|
|
#include "doclink.h"
|
|
#include "pipeline/pipeline.h"
|
|
#include "pipeline/pipeline_internal.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static const char *const ADOC_PAGE =
|
|
"= Writing Tests\n" /* 1 */
|
|
":testDir: example$java\n" /* 2 */
|
|
"\n" /* 3 */
|
|
"Intro with {Assertions} and {nbsp}.\n" /* 4 */
|
|
"\n" /* 5 */
|
|
"== First Test\n" /* 6 */
|
|
"\n" /* 7 */
|
|
"[source,java]\n" /* 8 */
|
|
"----\n" /* 9 */
|
|
"include::{testDir}/example/FirstTests.java[tags=user_guide]\n" /* 10 */
|
|
"== Not A Heading\n" /* 11 */
|
|
"----\n" /* 12 */
|
|
"\n" /* 13 */
|
|
"////\n" /* 14 */
|
|
"include::example$java/Hidden.java[]\n" /* 15 */
|
|
"////\n" /* 16 */
|
|
"// include::example$java/Commented.java[]\n" /* 17 */
|
|
"\n" /* 18 */
|
|
"=== Details\n" /* 19 */
|
|
"\n" /* 20 */
|
|
"See `src/main/java/org/x/Assertions.java` and `org.x.Assertions`.\n" /* 21 */
|
|
"include::../../../../src/main/java/org/x/Calc.java[lines=4..6]\n" /* 22 */
|
|
"include::{testDir}/example/WithImports.java[tag=all]\n"; /* 23 */
|
|
|
|
static const CBMDefinition *adoc_def(const CBMFileResult *r, const char *name) {
|
|
for (int i = 0; i < r->defs.count; i++) {
|
|
const CBMDefinition *d = &r->defs.items[i];
|
|
if (d->label && strcmp(d->label, "Section") == 0 && d->name && strcmp(d->name, name) == 0) {
|
|
return d;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool adoc_raw_has(const CBMFileResult *r, const char *needle) {
|
|
for (int i = 0; i < r->doc_links.count; i++) {
|
|
if (strstr(r->doc_links.items[i].raw, needle)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TEST(adoc_scan_structure) {
|
|
CBMFileResult *r =
|
|
dm_extract(ADOC_PAGE, CBM_LANG_ASCIIDOC, "docs/modules/ROOT/pages/writing.adoc");
|
|
ASSERT_NOT_NULL(r);
|
|
ASSERT_FALSE(r->doc_links.failed);
|
|
const CBMDefinition *top = adoc_def(r, "Writing Tests");
|
|
const CBMDefinition *first = adoc_def(r, "First Test");
|
|
const CBMDefinition *details = adoc_def(r, "Details");
|
|
ASSERT_NOT_NULL(top);
|
|
ASSERT_NOT_NULL(first);
|
|
ASSERT_NOT_NULL(details);
|
|
ASSERT_NULL(adoc_def(r, "Not A Heading")); /* inside a listing block */
|
|
ASSERT_EQ(first->start_line, 6);
|
|
ASSERT_EQ(first->end_line, 17);
|
|
ASSERT_STR_EQ(details->qualified_name, "p.docs.modules.ROOT.pages.writing.Details");
|
|
/* an include with the page's attribute substituted, its tags */
|
|
const CBMDocLink *inc =
|
|
dm_find_token(r, "include::{testDir}/example/FirstTests.java[tags=user_guide]\t"
|
|
"example$java/example/FirstTests.java\tuser_guide\t");
|
|
ASSERT_NOT_NULL(inc);
|
|
ASSERT_EQ(inc->syntax, CBM_DOCLINK_ADOC_INCLUDE);
|
|
ASSERT_EQ(inc->line, 10);
|
|
ASSERT_STR_EQ(inc->source_qn, first->qualified_name);
|
|
ASSERT_NOT_NULL(dm_find_token(r,
|
|
"include::../../../../src/main/java/org/x/Calc.java[lines=4..6]"
|
|
"\t../../../../src/main/java/org/x/Calc.java\t\t4..6"));
|
|
/* nothing from a comment block or a line comment */
|
|
ASSERT_FALSE(adoc_raw_has(r, "Hidden.java"));
|
|
ASSERT_FALSE(adoc_raw_has(r, "Commented.java"));
|
|
/* attribute references: built-ins are none; the page's own carry its value */
|
|
ASSERT_NOT_NULL(dm_find_token(r, "{Assertions}\tAssertions\t\t"));
|
|
ASSERT_FALSE(adoc_raw_has(r, "{nbsp}"));
|
|
/* monospace spans */
|
|
ASSERT_EQ(dm_find_token(r, "src/main/java/org/x/Assertions.java")->syntax,
|
|
CBM_DOCLINK_ADOC_CODE_PATH);
|
|
ASSERT_EQ(dm_find_token(r, "org.x.Assertions")->syntax, CBM_DOCLINK_ADOC_CODE_NAME);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
static const char *const ANTORA_YML =
|
|
"name: guide\n"
|
|
"version: true\n"
|
|
"ext:\n"
|
|
" collector:\n"
|
|
" scan:\n"
|
|
" - dir: ./build/generated\n"
|
|
" clean: true\n"
|
|
" - dir: ./src/test\n"
|
|
" into: modules/ROOT/examples\n"
|
|
"asciidoc:\n"
|
|
" attributes:\n"
|
|
" javadoc-root: \"xref:attachment$api\"\n"
|
|
" # API\n"
|
|
" Assertions: '{javadoc-root}/org.x/org/x/Assertions.html[Assertions]'\n"
|
|
" assertEquals: '{javadoc-root}/org.x/org/x/Assertions.html#assertEquals(int,int)[x]'\n"
|
|
" Missing: '{javadoc-root}/org.x/org/x/Gone.html[Gone]'\n";
|
|
|
|
TEST(adoc_antora_scope_blob) {
|
|
CBMFileResult *r = dm_extract(ANTORA_YML, CBM_LANG_YAML, "docs/antora.yml");
|
|
ASSERT_NOT_NULL(r);
|
|
ASSERT_NOT_NULL(r->doc_scope);
|
|
ASSERT_STR_EQ(r->doc_scope,
|
|
"ad1\n"
|
|
"C\n"
|
|
"N\tguide\n"
|
|
"D\t./build/generated\n"
|
|
"D\t./src/test\n"
|
|
"I\tmodules/ROOT/examples\n"
|
|
"A\tjavadoc-root\txref:attachment$api\n"
|
|
"A\tAssertions\t{javadoc-root}/org.x/org/x/Assertions.html[Assertions]\n"
|
|
"A\tassertEquals\t{javadoc-root}/org.x/org/x/Assertions.html#assertEquals(int,"
|
|
"int)[x]\n"
|
|
"A\tMissing\t{javadoc-root}/org.x/org/x/Gone.html[Gone]\n");
|
|
cbm_free_result(r);
|
|
r = dm_extract("site:\n title: x\n", CBM_LANG_YAML, "config/other.yml");
|
|
ASSERT_NOT_NULL(r);
|
|
ASSERT_NULL(r->doc_scope);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
static void adoc_write_repo(const char *repo, const char *first_tests) {
|
|
th_write_file(TH_PATH(repo, "docs/antora.yml"), ANTORA_YML);
|
|
th_write_file(TH_PATH(repo, "docs/modules/ROOT/pages/writing.adoc"), ADOC_PAGE);
|
|
th_write_file(TH_PATH(repo, "docs/src/test/java/example/FirstTests.java"), first_tests);
|
|
th_write_file(TH_PATH(repo, "src/main/java/org/x/Assertions.java"),
|
|
"package org.x;\n"
|
|
"\n"
|
|
"public class Assertions {\n"
|
|
" public static void assertEquals(int a, int b) {}\n"
|
|
" public static void fail() {}\n"
|
|
"}\n");
|
|
/* a tag region that takes the imports with the class: it binds the class */
|
|
th_write_file(TH_PATH(repo, "docs/src/test/java/example/WithImports.java"),
|
|
"package example;\n"
|
|
"\n"
|
|
"// tag::all[]\n"
|
|
"import java.util.List;\n"
|
|
"\n"
|
|
"class WithImports {\n"
|
|
" List<String> names() { return null; }\n"
|
|
"}\n"
|
|
"// end::all[]\n");
|
|
th_write_file(TH_PATH(repo, "src/main/java/org/x/Calc.java"),
|
|
"package org.x;\n"
|
|
"\n"
|
|
"public class Calc {\n"
|
|
" public int add(int a, int b) {\n"
|
|
" return a + b;\n"
|
|
" }\n"
|
|
"}\n");
|
|
}
|
|
|
|
static const char *const FIRST_TESTS = "package example;\n" /* 1 */
|
|
"\n" /* 2 */
|
|
"class FirstTests {\n" /* 3 */
|
|
" void helper() {}\n" /* 4 */
|
|
"\n" /* 5 */
|
|
" // tag::user_guide[]\n" /* 6 */
|
|
" void addition() {\n" /* 7 */
|
|
" helper();\n" /* 8 */
|
|
" }\n" /* 9 */
|
|
" // end::user_guide[]\n" /* 10 */
|
|
"}\n"; /* 11 */
|
|
|
|
static bool adoc_edge(const char *db, const char *src, const char *tgt, const char *needle) {
|
|
char props[512];
|
|
int n = 0;
|
|
dm_edge(db, src, tgt, props, sizeof(props), &n);
|
|
return n == 1 && (!needle || strstr(props, needle));
|
|
}
|
|
|
|
TEST(adoc_links_pipeline) {
|
|
char tmp[256];
|
|
snprintf(tmp, sizeof(tmp), "/tmp/cbm_dladoc_XXXXXX");
|
|
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
|
|
char repo[512];
|
|
char db[512];
|
|
snprintf(repo, sizeof(repo), "%s/repo", tmp);
|
|
snprintf(db, sizeof(db), "%s/g.db", tmp);
|
|
adoc_write_repo(repo, FIRST_TESTS);
|
|
ASSERT_EQ(dm_index(repo, db, NULL), 0);
|
|
const char *page = "docs.modules.ROOT.pages.writing";
|
|
char src[256];
|
|
/* a resource ID through a collector scan; the tag region's innermost method */
|
|
snprintf(src, sizeof(src), "%s.First-Test", page);
|
|
ASSERT_TRUE(adoc_edge(db, src, "example.FirstTests.addition",
|
|
"{\"via\":\"asciidoc\",\"syntax\":\"include\",\"tier\":\"exact\""));
|
|
ASSERT_TRUE(adoc_edge(db, src, "example.FirstTests.addition", "\"target_lines\":[7,9]"));
|
|
/* a javadoc attribute (under the document title): the type it documents */
|
|
snprintf(src, sizeof(src), "%s.Writing-Tests", page);
|
|
ASSERT_TRUE(adoc_edge(db, src, "org.x.Assertions", "\"syntax\":\"attribute\""));
|
|
/* lines= and a relative include */
|
|
snprintf(src, sizeof(src), "%s.Details", page);
|
|
ASSERT_TRUE(adoc_edge(db, src, "org.x.Calc.add", "\"target_lines\":[4,6]"));
|
|
/* the region's imports are no definition: the one it holds whole binds */
|
|
ASSERT_TRUE(adoc_edge(db, src, "example.WithImports", "\"target_lines\":[4,8]"));
|
|
/* monospace spans through the Markdown rules */
|
|
ASSERT_TRUE(adoc_edge(db, src, "src.main.java.org.x.Assertions.java.__file__",
|
|
"\"syntax\":\"code_path\""));
|
|
/* back navigation: the method knows the section that includes it */
|
|
ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM edges e JOIN nodes s ON s.id=e.source_id JOIN "
|
|
"nodes t ON t.id=e.target_id WHERE e.type='MENTIONS' AND "
|
|
"t.name='addition' AND s.label='Section' AND s.name='First Test'"),
|
|
1);
|
|
th_cleanup(tmp);
|
|
PASS();
|
|
}
|
|
|
|
TEST(adoc_links_incremental) {
|
|
char tmp[256];
|
|
snprintf(tmp, sizeof(tmp), "/tmp/cbm_dladoc_inc_XXXXXX");
|
|
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
|
|
char repo[512];
|
|
char db[512];
|
|
char full_db[512];
|
|
snprintf(repo, sizeof(repo), "%s/repo", tmp);
|
|
snprintf(db, sizeof(db), "%s/inc.db", tmp);
|
|
snprintf(full_db, sizeof(full_db), "%s/full.db", tmp);
|
|
adoc_write_repo(repo, FIRST_TESTS);
|
|
ASSERT_EQ(dm_index(repo, db, NULL), 0);
|
|
/* the tagged region moves to another method: the page follows it */
|
|
th_write_file(TH_PATH(repo, "docs/src/test/java/example/FirstTests.java"),
|
|
"package example;\n"
|
|
"\n"
|
|
"class FirstTests {\n"
|
|
" // tag::user_guide[]\n"
|
|
" void helper() {}\n"
|
|
" // end::user_guide[]\n"
|
|
"\n"
|
|
" void addition() {\n"
|
|
" helper();\n"
|
|
" }\n"
|
|
"}\n");
|
|
ASSERT_EQ(dm_step(repo, db, full_db, "tag moved", CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR), 0);
|
|
/* a component attribute changes: every page re-resolves */
|
|
th_write_file(TH_PATH(repo, "docs/antora.yml"),
|
|
"name: guide\n"
|
|
"ext:\n"
|
|
" collector:\n"
|
|
" scan:\n"
|
|
" - dir: ./src/test\n"
|
|
" into: modules/ROOT/examples\n"
|
|
"asciidoc:\n"
|
|
" attributes:\n"
|
|
" javadoc-root: \"xref:attachment$api\"\n"
|
|
" Assertions: '{javadoc-root}/org.x/org/x/Calc.html[Calc]'\n");
|
|
ASSERT_EQ(dm_step(repo, db, full_db, "attribute change", CBM_INCREMENTAL_ROUTE_FORCED_FULL), 0);
|
|
th_cleanup(tmp);
|
|
PASS();
|
|
}
|
|
|
|
SUITE(doc_links_adoc) {
|
|
RUN_TEST(adoc_scan_structure);
|
|
RUN_TEST(adoc_antora_scope_blob);
|
|
RUN_TEST(adoc_links_pipeline);
|
|
RUN_TEST(adoc_links_incremental);
|
|
}
|