mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-10 00:08:07 +02:00
nix repl: Render docs for attributes
This commit is contained in:
parent
491b9cf415
commit
d4f576b0b2
10 changed files with 237 additions and 3 deletions
|
@ -45,7 +45,7 @@ Examples
|
||||||
```
|
```
|
||||||
|
|
||||||
Known limitations:
|
Known limitations:
|
||||||
- It currently only works for functions. We plan to extend this to attributes, which may contain arbitrary values.
|
- It does not render documentation for "formals", such as `{ /** the value to return */ x, ... }: x`.
|
||||||
- Some extensions to markdown are not yet supported, as you can see in the example above.
|
- Some extensions to markdown are not yet supported, as you can see in the example above.
|
||||||
|
|
||||||
We'd like to acknowledge Yingchi Long for proposing a proof of concept for this functionality in [#9054](https://github.com/NixOS/nix/pull/9054), as well as @sternenseemann and Johannes Kirschbauer for their contributions, proposals, and their work on [RFC 145].
|
We'd like to acknowledge Yingchi Long for proposing a proof of concept for this functionality in [#9054](https://github.com/NixOS/nix/pull/9054), as well as @sternenseemann and Johannes Kirschbauer for their contributions, proposals, and their work on [RFC 145].
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
#include "print.hh"
|
#include "print.hh"
|
||||||
#include "ref.hh"
|
#include "ref.hh"
|
||||||
|
#include "value.hh"
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
#define GC_INCLUDE_NEW
|
#define GC_INCLUDE_NEW
|
||||||
|
@ -616,6 +617,33 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||||
|
|
||||||
else if (command == ":doc") {
|
else if (command == ":doc") {
|
||||||
Value v;
|
Value v;
|
||||||
|
|
||||||
|
auto expr = parseString(arg);
|
||||||
|
std::string fallbackName;
|
||||||
|
PosIdx fallbackPos;
|
||||||
|
DocComment fallbackDoc;
|
||||||
|
if (auto select = dynamic_cast<ExprSelect *>(expr)) {
|
||||||
|
Value vAttrs;
|
||||||
|
auto name = select->evalExceptFinalSelect(*state, *env, vAttrs);
|
||||||
|
fallbackName = state->symbols[name];
|
||||||
|
|
||||||
|
state->forceAttrs(vAttrs, noPos, "while evaluating an attribute set to look for documentation");
|
||||||
|
auto attrs = vAttrs.attrs();
|
||||||
|
assert(attrs);
|
||||||
|
auto attr = attrs->get(name);
|
||||||
|
if (!attr) {
|
||||||
|
// Trigger the normal error
|
||||||
|
evalString(arg, v);
|
||||||
|
}
|
||||||
|
if (attr->pos) {
|
||||||
|
fallbackPos = attr->pos;
|
||||||
|
fallbackDoc = state->getDocCommentForPos(fallbackPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
evalString(arg, v);
|
||||||
|
}
|
||||||
|
|
||||||
evalString(arg, v);
|
evalString(arg, v);
|
||||||
if (auto doc = state->getDoc(v)) {
|
if (auto doc = state->getDoc(v)) {
|
||||||
std::string markdown;
|
std::string markdown;
|
||||||
|
@ -633,6 +661,19 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||||
markdown += stripIndentation(doc->doc);
|
markdown += stripIndentation(doc->doc);
|
||||||
|
|
||||||
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||||
|
} else if (fallbackPos) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Attribute `" << fallbackName << "`\n\n";
|
||||||
|
ss << " … defined at " << state->positions[fallbackPos] << "\n\n";
|
||||||
|
if (fallbackDoc) {
|
||||||
|
ss << fallbackDoc.getInnerText(state->positions);
|
||||||
|
} else {
|
||||||
|
ss << "No documentation found.\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto markdown = ss.str();
|
||||||
|
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||||
|
|
||||||
} else
|
} else
|
||||||
throw Error("value does not have documentation");
|
throw Error("value does not have documentation");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1415,6 +1415,22 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
v = *vAttrs;
|
v = *vAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Symbol ExprSelect::evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs)
|
||||||
|
{
|
||||||
|
Value vTmp;
|
||||||
|
Symbol name = getName(attrPath[attrPath.size() - 1], state, env);
|
||||||
|
|
||||||
|
if (attrPath.size() == 1) {
|
||||||
|
e->eval(state, env, vTmp);
|
||||||
|
} else {
|
||||||
|
ExprSelect init(*this);
|
||||||
|
init.attrPath.pop_back();
|
||||||
|
init.eval(state, env, vTmp);
|
||||||
|
}
|
||||||
|
attrs = vTmp;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
|
@ -2876,13 +2892,37 @@ Expr * EvalState::parse(
|
||||||
const SourcePath & basePath,
|
const SourcePath & basePath,
|
||||||
std::shared_ptr<StaticEnv> & staticEnv)
|
std::shared_ptr<StaticEnv> & staticEnv)
|
||||||
{
|
{
|
||||||
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, rootFS, exprSymbols);
|
DocCommentMap tmpDocComments; // Only used when not origin is not a SourcePath
|
||||||
|
DocCommentMap *docComments = &tmpDocComments;
|
||||||
|
|
||||||
|
if (auto sourcePath = std::get_if<SourcePath>(&origin)) {
|
||||||
|
auto [it, _] = positionToDocComment.try_emplace(*sourcePath);
|
||||||
|
docComments = &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, *docComments, rootFS, exprSymbols);
|
||||||
|
|
||||||
result->bindVars(*this, staticEnv);
|
result->bindVars(*this, staticEnv);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DocComment EvalState::getDocCommentForPos(PosIdx pos)
|
||||||
|
{
|
||||||
|
auto pos2 = positions[pos];
|
||||||
|
auto path = pos2.getSourcePath();
|
||||||
|
if (!path)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto table = positionToDocComment.find(*path);
|
||||||
|
if (table == positionToDocComment.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto it = table->second.find(pos);
|
||||||
|
if (it == table->second.end())
|
||||||
|
return {};
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -130,6 +130,8 @@ struct Constant
|
||||||
typedef std::map<std::string, Value *> ValMap;
|
typedef std::map<std::string, Value *> ValMap;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef std::map<PosIdx, DocComment> DocCommentMap;
|
||||||
|
|
||||||
struct Env
|
struct Env
|
||||||
{
|
{
|
||||||
Env * up;
|
Env * up;
|
||||||
|
@ -329,6 +331,12 @@ private:
|
||||||
#endif
|
#endif
|
||||||
FileEvalCache fileEvalCache;
|
FileEvalCache fileEvalCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate source positions of certain AST nodes with their preceding doc comment, if they have one.
|
||||||
|
* Grouped by file.
|
||||||
|
*/
|
||||||
|
std::map<SourcePath, DocCommentMap> positionToDocComment;
|
||||||
|
|
||||||
LookupPath lookupPath;
|
LookupPath lookupPath;
|
||||||
|
|
||||||
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
||||||
|
@ -771,6 +779,8 @@ public:
|
||||||
std::string_view pathArg,
|
std::string_view pathArg,
|
||||||
PosIdx pos);
|
PosIdx pos);
|
||||||
|
|
||||||
|
DocComment getDocCommentForPos(PosIdx pos);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -202,6 +202,17 @@ struct ExprSelect : Expr
|
||||||
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
|
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
|
||||||
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
||||||
PosIdx getPos() const override { return pos; }
|
PosIdx getPos() const override { return pos; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
|
||||||
|
*
|
||||||
|
* @param[out] v The attribute set that should contain the last attribute name (if it exists).
|
||||||
|
* @return The last attribute name in `attrPath`
|
||||||
|
*
|
||||||
|
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist.
|
||||||
|
*/
|
||||||
|
Symbol evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs);
|
||||||
|
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ struct LexerState
|
||||||
/**
|
/**
|
||||||
* @brief Maps some positions to a DocComment, where the comment is relevant to the location.
|
* @brief Maps some positions to a DocComment, where the comment is relevant to the location.
|
||||||
*/
|
*/
|
||||||
std::map<PosIdx, DocComment> positionToDocComment;
|
std::map<PosIdx, DocComment> & positionToDocComment;
|
||||||
|
|
||||||
PosTable & positions;
|
PosTable & positions;
|
||||||
PosTable::Origin origin;
|
PosTable::Origin origin;
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
typedef std::map<PosIdx, DocComment> DocCommentMap;
|
||||||
|
|
||||||
Expr * parseExprFromBuf(
|
Expr * parseExprFromBuf(
|
||||||
char * text,
|
char * text,
|
||||||
size_t length,
|
size_t length,
|
||||||
|
@ -41,6 +43,7 @@ Expr * parseExprFromBuf(
|
||||||
SymbolTable & symbols,
|
SymbolTable & symbols,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
PosTable & positions,
|
PosTable & positions,
|
||||||
|
DocCommentMap & docComments,
|
||||||
const ref<SourceAccessor> rootFS,
|
const ref<SourceAccessor> rootFS,
|
||||||
const Expr::AstSymbols & astSymbols);
|
const Expr::AstSymbols & astSymbols);
|
||||||
|
|
||||||
|
@ -335,10 +338,12 @@ binds
|
||||||
$$ = $1;
|
$$ = $1;
|
||||||
|
|
||||||
auto pos = state->at(@2);
|
auto pos = state->at(@2);
|
||||||
|
auto exprPos = state->at(@4);
|
||||||
{
|
{
|
||||||
auto it = state->lexerState.positionToDocComment.find(pos);
|
auto it = state->lexerState.positionToDocComment.find(pos);
|
||||||
if (it != state->lexerState.positionToDocComment.end()) {
|
if (it != state->lexerState.positionToDocComment.end()) {
|
||||||
$4->setDocComment(it->second);
|
$4->setDocComment(it->second);
|
||||||
|
state->lexerState.positionToDocComment.emplace(exprPos, it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,11 +468,13 @@ Expr * parseExprFromBuf(
|
||||||
SymbolTable & symbols,
|
SymbolTable & symbols,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
PosTable & positions,
|
PosTable & positions,
|
||||||
|
DocCommentMap & docComments,
|
||||||
const ref<SourceAccessor> rootFS,
|
const ref<SourceAccessor> rootFS,
|
||||||
const Expr::AstSymbols & astSymbols)
|
const Expr::AstSymbols & astSymbols)
|
||||||
{
|
{
|
||||||
yyscan_t scanner;
|
yyscan_t scanner;
|
||||||
LexerState lexerState {
|
LexerState lexerState {
|
||||||
|
.positionToDocComment = docComments,
|
||||||
.positions = positions,
|
.positions = positions,
|
||||||
.origin = positions.addOrigin(origin, length),
|
.origin = positions.addOrigin(origin, length),
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include "source-path.hh"
|
#include "source-path.hh"
|
||||||
|
|
||||||
|
@ -65,6 +66,13 @@ struct Pos
|
||||||
|
|
||||||
std::string getSnippetUpTo(const Pos & end) const;
|
std::string getSnippetUpTo(const Pos & end) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SourcePath, if the source was loaded from a file.
|
||||||
|
*/
|
||||||
|
std::optional<SourcePath> getSourcePath() const {
|
||||||
|
return *std::get_if<SourcePath>(&origin);
|
||||||
|
}
|
||||||
|
|
||||||
struct LinesIterator {
|
struct LinesIterator {
|
||||||
using difference_type = size_t;
|
using difference_type = size_t;
|
||||||
using value_type = std::string_view;
|
using value_type = std::string_view;
|
||||||
|
|
102
tests/functional/repl/doc-constant.expected
Normal file
102
tests/functional/repl/doc-constant.expected
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
error: value does not have documentation
|
||||||
|
|
||||||
|
Attribute version
|
||||||
|
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:29:3
|
||||||
|
|
||||||
|
Immovably fixed.
|
||||||
|
|
||||||
|
Attribute empty
|
||||||
|
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:32:3
|
||||||
|
|
||||||
|
Unchangeably constant.
|
||||||
|
|
||||||
|
error:
|
||||||
|
… while evaluating the attribute 'attr.undocument'
|
||||||
|
at /path/to/tests/functional/repl/doc-comments.nix:32:3:
|
||||||
|
31| /** Unchangeably constant. */
|
||||||
|
32| lib.attr.empty = { };
|
||||||
|
| ^
|
||||||
|
33|
|
||||||
|
|
||||||
|
error: attribute 'undocument' missing
|
||||||
|
at «string»:1:1:
|
||||||
|
1| lib.attr.undocument
|
||||||
|
| ^
|
||||||
|
Did you mean undocumented?
|
||||||
|
|
||||||
|
Attribute constant
|
||||||
|
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:26:3
|
||||||
|
|
||||||
|
Firmly rigid.
|
||||||
|
|
||||||
|
Attribute version
|
||||||
|
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:29:3
|
||||||
|
|
||||||
|
Immovably fixed.
|
||||||
|
|
||||||
|
Attribute empty
|
||||||
|
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:32:3
|
||||||
|
|
||||||
|
Unchangeably constant.
|
||||||
|
|
||||||
|
Attribute undocumented
|
||||||
|
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:34:3
|
||||||
|
|
||||||
|
No documentation found.
|
||||||
|
|
||||||
|
error: undefined variable 'missing'
|
||||||
|
at «string»:1:1:
|
||||||
|
1| missing
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: undefined variable 'constanz'
|
||||||
|
at «string»:1:1:
|
||||||
|
1| constanz
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: undefined variable 'missing'
|
||||||
|
at «string»:1:1:
|
||||||
|
1| missing.attr
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: attribute 'missing' missing
|
||||||
|
at «string»:1:1:
|
||||||
|
1| lib.missing
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: attribute 'missing' missing
|
||||||
|
at «string»:1:1:
|
||||||
|
1| lib.missing.attr
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error:
|
||||||
|
… while evaluating the attribute 'attr.undocumental'
|
||||||
|
at /path/to/tests/functional/repl/doc-comments.nix:32:3:
|
||||||
|
31| /** Unchangeably constant. */
|
||||||
|
32| lib.attr.empty = { };
|
||||||
|
| ^
|
||||||
|
33|
|
||||||
|
|
||||||
|
error: attribute 'undocumental' missing
|
||||||
|
at «string»:1:1:
|
||||||
|
1| lib.attr.undocumental
|
||||||
|
| ^
|
||||||
|
Did you mean undocumented?
|
||||||
|
|
||||||
|
|
15
tests/functional/repl/doc-constant.in
Normal file
15
tests/functional/repl/doc-constant.in
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc constant
|
||||||
|
:doc lib.version
|
||||||
|
:doc lib.attr.empty
|
||||||
|
:doc lib.attr.undocument
|
||||||
|
:doc (import ./doc-comments.nix).constant
|
||||||
|
:doc (import ./doc-comments.nix).lib.version
|
||||||
|
:doc (import ./doc-comments.nix).lib.attr.empty
|
||||||
|
:doc (import ./doc-comments.nix).lib.attr.undocumented
|
||||||
|
:doc missing
|
||||||
|
:doc constanz
|
||||||
|
:doc missing.attr
|
||||||
|
:doc lib.missing
|
||||||
|
:doc lib.missing.attr
|
||||||
|
:doc lib.attr.undocumental
|
Loading…
Reference in a new issue