mirror of
https://github.com/NixOS/nix.git
synced 2024-11-25 08:12:29 +00:00
nix repl: Render docs for attributes
This commit is contained in:
parent
491b9cf415
commit
d4f576b0b2
@ -45,7 +45,7 @@ Examples
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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 "print.hh"
|
||||
#include "ref.hh"
|
||||
#include "value.hh"
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#define GC_INCLUDE_NEW
|
||||
@ -616,6 +617,33 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||
|
||||
else if (command == ":doc") {
|
||||
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);
|
||||
if (auto doc = state->getDoc(v)) {
|
||||
std::string markdown;
|
||||
@ -633,6 +661,19 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||
markdown += stripIndentation(doc->doc);
|
||||
|
||||
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
|
||||
throw Error("value does not have documentation");
|
||||
}
|
||||
|
@ -1415,6 +1415,22 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||
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)
|
||||
{
|
||||
@ -2876,13 +2892,37 @@ Expr * EvalState::parse(
|
||||
const SourcePath & basePath,
|
||||
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);
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -130,6 +130,8 @@ struct Constant
|
||||
typedef std::map<std::string, Value *> ValMap;
|
||||
#endif
|
||||
|
||||
typedef std::map<PosIdx, DocComment> DocCommentMap;
|
||||
|
||||
struct Env
|
||||
{
|
||||
Env * up;
|
||||
@ -329,6 +331,12 @@ private:
|
||||
#endif
|
||||
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;
|
||||
|
||||
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
||||
@ -771,6 +779,8 @@ public:
|
||||
std::string_view pathArg,
|
||||
PosIdx pos);
|
||||
|
||||
DocComment getDocCommentForPos(PosIdx pos);
|
||||
|
||||
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, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
||||
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
|
||||
};
|
||||
|
||||
|
@ -64,7 +64,7 @@ struct LexerState
|
||||
/**
|
||||
* @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::Origin origin;
|
||||
|
@ -33,6 +33,8 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef std::map<PosIdx, DocComment> DocCommentMap;
|
||||
|
||||
Expr * parseExprFromBuf(
|
||||
char * text,
|
||||
size_t length,
|
||||
@ -41,6 +43,7 @@ Expr * parseExprFromBuf(
|
||||
SymbolTable & symbols,
|
||||
const EvalSettings & settings,
|
||||
PosTable & positions,
|
||||
DocCommentMap & docComments,
|
||||
const ref<SourceAccessor> rootFS,
|
||||
const Expr::AstSymbols & astSymbols);
|
||||
|
||||
@ -335,10 +338,12 @@ binds
|
||||
$$ = $1;
|
||||
|
||||
auto pos = state->at(@2);
|
||||
auto exprPos = state->at(@4);
|
||||
{
|
||||
auto it = state->lexerState.positionToDocComment.find(pos);
|
||||
if (it != state->lexerState.positionToDocComment.end()) {
|
||||
$4->setDocComment(it->second);
|
||||
state->lexerState.positionToDocComment.emplace(exprPos, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,11 +468,13 @@ Expr * parseExprFromBuf(
|
||||
SymbolTable & symbols,
|
||||
const EvalSettings & settings,
|
||||
PosTable & positions,
|
||||
DocCommentMap & docComments,
|
||||
const ref<SourceAccessor> rootFS,
|
||||
const Expr::AstSymbols & astSymbols)
|
||||
{
|
||||
yyscan_t scanner;
|
||||
LexerState lexerState {
|
||||
.positionToDocComment = docComments,
|
||||
.positions = positions,
|
||||
.origin = positions.addOrigin(origin, length),
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "source-path.hh"
|
||||
|
||||
@ -65,6 +66,13 @@ struct Pos
|
||||
|
||||
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 {
|
||||
using difference_type = size_t;
|
||||
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 New Issue
Block a user