Logic for determining expression hover
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
DescriptionDictionary,
|
||||
Evaluator,
|
||||
isDescriptionDictionary,
|
||||
wellKnownFunctions
|
||||
} from "@github/actions-expressions";
|
||||
import {
|
||||
Binary,
|
||||
ContextAccess,
|
||||
Expr,
|
||||
ExprVisitor,
|
||||
FunctionCall,
|
||||
Grouping,
|
||||
IndexAccess,
|
||||
Literal,
|
||||
Logical,
|
||||
Unary
|
||||
} from "@github/actions-expressions/ast";
|
||||
import {FunctionDefinition, FunctionInfo} from "@github/actions-expressions/funcs/info";
|
||||
import {Pos, Range} from "@github/actions-expressions/lexer";
|
||||
import {posWithinRange} from "./pos-range";
|
||||
|
||||
export type HoverResult =
|
||||
| undefined
|
||||
| {
|
||||
label: string;
|
||||
description?: string;
|
||||
function: boolean;
|
||||
range: Range;
|
||||
};
|
||||
|
||||
export class HoverVisitor implements ExprVisitor<HoverResult> {
|
||||
private ignorePosCheck = false;
|
||||
|
||||
constructor(
|
||||
private pos: Pos,
|
||||
private context: DescriptionDictionary,
|
||||
private extensionFunctions: FunctionInfo[],
|
||||
private functions: Map<string, FunctionDefinition>
|
||||
) {}
|
||||
|
||||
hover(n: Expr): HoverResult {
|
||||
return n.accept(this);
|
||||
}
|
||||
|
||||
visitLiteral(literal: Literal): HoverResult {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
visitUnary(unary: Unary): HoverResult {
|
||||
return this.hover(unary.expr);
|
||||
}
|
||||
|
||||
visitBinary(binary: Binary): HoverResult {
|
||||
return this.hover(binary.left) || this.hover(binary.right);
|
||||
}
|
||||
|
||||
visitLogical(logical: Logical): HoverResult {
|
||||
for (const arg of logical.args) {
|
||||
const result = this.hover(arg);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
visitGrouping(grouping: Grouping): HoverResult {
|
||||
return this.hover(grouping.group);
|
||||
}
|
||||
|
||||
visitContextAccess(contextAccess: ContextAccess): HoverResult {
|
||||
if (this.ignorePosCheck || posWithinRange(this.pos, contextAccess.name.range)) {
|
||||
const contextName = contextAccess.name.lexeme;
|
||||
|
||||
return {
|
||||
label: contextName,
|
||||
description: this.context.getDescription(contextName),
|
||||
function: false,
|
||||
range: contextAccess.name.range
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
visitIndexAccess(indexAccess: IndexAccess): HoverResult {
|
||||
// Is the position within the index, so for example:
|
||||
// github.event.test
|
||||
// ^ - pos
|
||||
if (!(indexAccess.index instanceof Literal)) {
|
||||
// No support for context access of the form github[github.event]
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!posWithinRange(this.pos, indexAccess.index.token.range)) {
|
||||
// Try to get hover from the rest of the expression
|
||||
return this.hover(indexAccess.expr);
|
||||
}
|
||||
|
||||
const ev = new Evaluator(indexAccess.expr, this.context, this.functions);
|
||||
const result = ev.evaluate();
|
||||
|
||||
if (!isDescriptionDictionary(result)) {
|
||||
// No description to show
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const key = indexAccess.index.literal.coerceString();
|
||||
const description = result.getDescription(key);
|
||||
if (!description) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Calculate context access range for whole expression. For example:
|
||||
// github.event.test
|
||||
// ^ - pos
|
||||
// should return the range:
|
||||
// github.event.test
|
||||
// ^^^^^^^^^^^^
|
||||
this.ignorePosCheck = true;
|
||||
|
||||
try {
|
||||
const contextHover = this.hover(indexAccess.expr);
|
||||
if (!contextHover) {
|
||||
throw new Error("Expected context hover to be defined");
|
||||
}
|
||||
|
||||
return {
|
||||
label: key,
|
||||
description: description,
|
||||
function: false,
|
||||
range: {
|
||||
start: contextHover.range.start,
|
||||
end: indexAccess.index.token.range.end
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
this.ignorePosCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
visitFunctionCall(functionCall: FunctionCall): HoverResult {
|
||||
if (posWithinRange(this.pos, functionCall.functionName.range)) {
|
||||
const functionName = functionCall.functionName.lexeme.toLowerCase();
|
||||
const f = this.functions.get(functionName) || wellKnownFunctions[functionName];
|
||||
|
||||
return {
|
||||
label: f.name,
|
||||
description: f.description,
|
||||
function: true,
|
||||
range: functionCall.functionName.range
|
||||
};
|
||||
}
|
||||
|
||||
for (const args of functionCall.args) {
|
||||
const result = this.hover(args);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user