This commit is contained in:
Jan Jurzitza 2023-08-11 16:31:45 +02:00 committed by GitHub
commit 6406a8fb5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 166 additions and 19 deletions

View File

@ -85,6 +85,11 @@ final class FirstPass : ASTVisitor
override void visit(const Unittest u) override void visit(const Unittest u)
{ {
if (previousSymbol && previousSymbol.acSymbol)
makeExampleDocumentation(u, previousSymbol.acSymbol.doc);
auto associated = previousSymbol;
scope(exit) previousSymbol = associated;
// Create a dummy symbol because we don't want unit test symbols leaking // Create a dummy symbol because we don't want unit test symbols leaking
// into the symbol they're declared in. // into the symbol they're declared in.
pushSymbol(UNITTEST_SYMBOL_NAME, pushSymbol(UNITTEST_SYMBOL_NAME,
@ -130,7 +135,7 @@ final class FirstPass : ASTVisitor
dec.name.index, dec.returnType); dec.name.index, dec.returnType);
scope (exit) popSymbol(); scope (exit) popSymbol();
currentSymbol.acSymbol.protection = protection.current; currentSymbol.acSymbol.protection = protection.current;
currentSymbol.acSymbol.doc = makeDocumentation(dec.comment); makeDocumentation(currentSymbol.acSymbol.doc, dec.comment);
istring lastComment = this.lastComment; istring lastComment = this.lastComment;
this.lastComment = istring.init; this.lastComment = istring.init;
@ -250,7 +255,7 @@ final class FirstPass : ASTVisitor
addTypeToLookups(symbol.typeLookups, dec.type); addTypeToLookups(symbol.typeLookups, dec.type);
symbol.parent = currentSymbol; symbol.parent = currentSymbol;
symbol.acSymbol.protection = protection.current; symbol.acSymbol.protection = protection.current;
symbol.acSymbol.doc = makeDocumentation(declarator.comment); makeDocumentation(symbol.acSymbol.doc, declarator.comment);
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
currentScope.addSymbol(symbol.acSymbol, false); currentScope.addSymbol(symbol.acSymbol, false);
@ -272,7 +277,7 @@ final class FirstPass : ASTVisitor
symbol.parent = currentSymbol; symbol.parent = currentSymbol;
populateInitializer(symbol, part.initializer); populateInitializer(symbol, part.initializer);
symbol.acSymbol.protection = protection.current; symbol.acSymbol.protection = protection.current;
symbol.acSymbol.doc = makeDocumentation(dec.comment); makeDocumentation(symbol.acSymbol.doc, dec.comment);
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
currentScope.addSymbol(symbol.acSymbol, false); currentScope.addSymbol(symbol.acSymbol, false);
@ -301,7 +306,7 @@ final class FirstPass : ASTVisitor
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
currentScope.addSymbol(symbol.acSymbol, false); currentScope.addSymbol(symbol.acSymbol, false);
symbol.acSymbol.protection = protection.current; symbol.acSymbol.protection = protection.current;
symbol.acSymbol.doc = makeDocumentation(aliasDeclaration.comment); makeDocumentation(symbol.acSymbol.doc, aliasDeclaration.comment);
} }
} }
else else
@ -317,7 +322,7 @@ final class FirstPass : ASTVisitor
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
currentScope.addSymbol(symbol.acSymbol, false); currentScope.addSymbol(symbol.acSymbol, false);
symbol.acSymbol.protection = protection.current; symbol.acSymbol.protection = protection.current;
symbol.acSymbol.doc = makeDocumentation(aliasDeclaration.comment); makeDocumentation(symbol.acSymbol.doc, aliasDeclaration.comment);
} }
} }
} }
@ -399,7 +404,7 @@ final class FirstPass : ASTVisitor
symbol.parent = currentSymbol; symbol.parent = currentSymbol;
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
currentScope.addSymbol(symbol.acSymbol, false); currentScope.addSymbol(symbol.acSymbol, false);
symbol.acSymbol.doc = makeDocumentation(dec.comment); makeDocumentation(symbol.acSymbol.doc, dec.comment);
istring lastComment = this.lastComment; istring lastComment = this.lastComment;
this.lastComment = istring.init; this.lastComment = istring.init;
@ -415,6 +420,7 @@ final class FirstPass : ASTVisitor
} }
currentSymbol = currentSymbol.parent; currentSymbol = currentSymbol.parent;
previousSymbol = symbol;
} }
mixin visitEnumMember!EnumMember; mixin visitEnumMember!EnumMember;
@ -847,11 +853,15 @@ private:
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
currentScope.addSymbol(symbol.acSymbol, false); currentScope.addSymbol(symbol.acSymbol, false);
currentSymbol = symbol; currentSymbol = symbol;
pushedSymbolsStack.assumeSafeAppend ~= symbol;
} }
void popSymbol() void popSymbol()
{ {
assert(pushedSymbolsStack.length, "called popSymbol without pushSymbol");
currentSymbol = currentSymbol.parent; currentSymbol = currentSymbol.parent;
previousSymbol = pushedSymbolsStack[$ - 1];
pushedSymbolsStack.length--;
} }
template visitEnumMember(T) template visitEnumMember(T)
@ -861,7 +871,7 @@ private:
pushSymbol(member.name.text, CompletionKind.enumMember, symbolFile, pushSymbol(member.name.text, CompletionKind.enumMember, symbolFile,
member.name.index, member.type); member.name.index, member.type);
scope(exit) popSymbol(); scope(exit) popSymbol();
currentSymbol.acSymbol.doc = makeDocumentation(member.comment); makeDocumentation(currentSymbol.acSymbol.doc, member.comment);
} }
} }
@ -881,7 +891,7 @@ private:
else else
currentSymbol.acSymbol.addChildren(aggregateSymbols[], false); currentSymbol.acSymbol.addChildren(aggregateSymbols[], false);
currentSymbol.acSymbol.protection = protection.current; currentSymbol.acSymbol.protection = protection.current;
currentSymbol.acSymbol.doc = makeDocumentation(dec.comment); makeDocumentation(currentSymbol.acSymbol.doc, dec.comment);
istring lastComment = this.lastComment; istring lastComment = this.lastComment;
this.lastComment = istring.init; this.lastComment = istring.init;
@ -910,7 +920,7 @@ private:
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
processParameters(symbol, null, THIS_SYMBOL_NAME, parameters, templateParameters); processParameters(symbol, null, THIS_SYMBOL_NAME, parameters, templateParameters);
symbol.acSymbol.protection = protection.current; symbol.acSymbol.protection = protection.current;
symbol.acSymbol.doc = makeDocumentation(doc); makeDocumentation(symbol.acSymbol.doc, doc);
istring lastComment = this.lastComment; istring lastComment = this.lastComment;
this.lastComment = istring.init; this.lastComment = istring.init;
@ -923,6 +933,7 @@ private:
currentSymbol = symbol; currentSymbol = symbol;
functionBody.accept(this); functionBody.accept(this);
currentSymbol = currentSymbol.parent; currentSymbol = currentSymbol.parent;
previousSymbol = symbol;
} }
} }
@ -934,7 +945,7 @@ private:
currentSymbol.addChild(symbol, true); currentSymbol.addChild(symbol, true);
symbol.acSymbol.callTip = internString("~this()"); symbol.acSymbol.callTip = internString("~this()");
symbol.acSymbol.protection = protection.current; symbol.acSymbol.protection = protection.current;
symbol.acSymbol.doc = makeDocumentation(doc); makeDocumentation(symbol.acSymbol.doc, doc);
istring lastComment = this.lastComment; istring lastComment = this.lastComment;
this.lastComment = istring.init; this.lastComment = istring.init;
@ -947,6 +958,7 @@ private:
currentSymbol = symbol; currentSymbol = symbol;
functionBody.accept(this); functionBody.accept(this);
currentSymbol = currentSymbol.parent; currentSymbol = currentSymbol.parent;
previousSymbol = symbol;
} }
} }
@ -1189,14 +1201,85 @@ private:
lookups.insert(lookup); lookups.insert(lookup);
} }
DocString makeDocumentation(string documentation) void makeDocumentation(ref DocString into, string documentation)
{ {
if (documentation.isDitto) if (documentation.isDitto)
return DocString(lastComment, true); {
into = DocString(lastComment, true);
into.dittoOf = lastDocStringInstance;
lastDocStringInstance = &into;
}
else else
{ {
lastComment = internString(documentation); lastComment = internString(documentation);
return DocString(lastComment, false); into = DocString(lastComment, false);
lastDocStringInstance = &into;
}
}
static makeExampleDocumentation(const Unittest block, ref DocString doc)
{
import dparse.trivia;
import std.algorithm;
import std.array;
import std.string;
auto tokens = block.tokens;
if (tokens.length)
{
if (block.comment !is null)
{
auto data = appender!string;
data ~= "Examples:\n\n";
if (block.comment.length)
{
data ~= block.comment;
data ~= "\n\n";
}
data ~= "---\n";
assert(tokens.length >= 3);
auto unittestTok = tokens.countUntil!(t => t.type == tok!"unittest");
assert(unittestTok != -1);
auto openingTok = tokens[unittestTok .. $].countUntil!(t => t.type == tok!"{");
assert(openingTok != -1);
openingTok += unittestTok;
assert(tokens[$ - 1].type == tok!"}");
auto codeData = appender!string;
foreach (trailingStart; tokens[openingTok].trailingTrivia)
codeData ~= trailingStart.text;
size_t currentLine = size_t.max;
foreach (token; tokens[openingTok + 1 .. $ - 1])
{
currentLine = token.line;
foreach (leading; token.leadingTrivia)
codeData ~= leading.text;
if (token.text.length)
codeData ~= token.text;
else
codeData ~= str(token.type);
foreach (trailing; token.trailingTrivia)
codeData ~= trailing.text;
}
foreach (leadingEnd; tokens[$ - 1].leadingTrivia)
codeData ~= leadingEnd.text;
data ~= codeData.data.outdent.chompPrefix("\n").chomp("\n");
data ~= "\n---\n";
DocString* s = &doc;
while (s)
{
s.examples ~= istring(data.data);
s = s.dittoOf;
}
}
} }
} }
@ -1207,7 +1290,10 @@ private:
Scope* currentScope; Scope* currentScope;
/// Current symbol /// Current symbol
SemanticSymbol* currentSymbol; SemanticSymbol* currentSymbol, previousSymbol;
/// Stack of semantic symbols, for referencing the previousSymbol from popSymbol
SemanticSymbol*[] pushedSymbolsStack;
/// Path to the file being converted /// Path to the file being converted
istring symbolFile; istring symbolFile;
@ -1224,6 +1310,8 @@ private:
/// Last comment for ditto-ing /// Last comment for ditto-ing
istring lastComment; istring lastComment;
DocString* lastDocStringInstance;
const Module mod; const Module mod;
Rebindable!(const ExpressionNode) feExpression; Rebindable!(const ExpressionNode) feExpression;

View File

@ -149,10 +149,15 @@ class SimpleParser : Parser
{ {
override Unittest parseUnittest() override Unittest parseUnittest()
{ {
auto start = index;
expect(tok!"unittest"); expect(tok!"unittest");
if (currentIs(tok!"{")) if (currentIs(tok!"{"))
skipBraces(); skipBraces();
return allocator.make!Unittest; auto ret = allocator.make!Unittest;
ret.tokens = tokens[start .. index];
ret.comment = comment;
comment = null;
return ret;
} }
override MissingFunctionBody parseMissingFunctionBody() override MissingFunctionBody parseMissingFunctionBody()

View File

@ -524,22 +524,40 @@ struct DocString
/// Creates a non-ditto comment. /// Creates a non-ditto comment.
this(istring content) this(istring content)
{ {
this.content = content; this.rawContent = content;
} }
/// Creates a comment which may have been ditto, but has been resolved. /// Creates a comment which may have been ditto, but has been resolved.
this(istring content, bool ditto) this(istring content, bool ditto)
{ {
this.content = content; this.rawContent = content;
this.ditto = ditto; this.ditto = ditto;
} }
alias content this; alias toString this;
deprecated("use toString to get a full formatted doc string or rawContent for just what is applied directly (or ditto'd) on the function") alias content = rawContent;
/// Contains the documentation string associated with this symbol, resolves ditto to the previous comment with correct scope. /// Contains the documentation string associated with this symbol, resolves ditto to the previous comment with correct scope.
istring content; istring rawContent;
/// `true` if the documentation was just a "ditto" comment copying from the previous comment. /// `true` if the documentation was just a "ditto" comment copying from the previous comment.
bool ditto; bool ditto;
/// Contains the source code + docstring for each documented unittest example associated with this symbol.
istring[] examples;
// package-private because we don't want to overcomplicate the lifetime in
// the public API. (This might point to a broken address later)
package(dsymbol) DocString* dittoOf;
string toString() const @safe pure
{
import std.algorithm;
import std.array;
return examples.length
? (rawContent ~ "\n\n" ~ examples.map!"a.data".join("\n"))
: rawContent;
}
} }
struct UpdatePair struct UpdatePair

View File

@ -0,0 +1 @@
Does foo stuff.\n\nExamples:\n\n---\n// usable with ints\nfoo(1);\n// and with strings!\nif (auto line = readln())\n foo(line);\n\n// or here\nfoo( 1+2 );\n---\n\nExamples:\n\nsecond usage works too\n\n---\nfoo();\n---\n

View File

@ -0,0 +1,30 @@
void main()
{
foo(1);
}
/// Does foo stuff.
template foo()
{
void foo(int a) {}
void foo(string b) {}
}
///
unittest
{
// usable with ints
foo(1);
// and with strings!
if (auto line = readln())
foo(line);
// or here
foo( 1+2 );
}
/// second usage works too
unittest
{
foo();
}

5
tests/tc_unittest_docs/run.sh Executable file
View File

@ -0,0 +1,5 @@
set -e
set -u
../../bin/dcd-client $1 file.d -d -c20 > actual1.txt
diff actual1.txt expected1.txt