167 lines
4.1 KiB
TypeScript
167 lines
4.1 KiB
TypeScript
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;
|
|
}
|
|
}
|