Integrate expression hover
This commit is contained in:
@@ -1,37 +1,72 @@
|
||||
import {DescriptionDictionary, Parser} from "@github/actions-expressions";
|
||||
import {FunctionInfo} from "@github/actions-expressions/funcs/info";
|
||||
import {Lexer} from "@github/actions-expressions/lexer";
|
||||
import {convertWorkflowTemplate, parseWorkflow, ParseWorkflowResult} from "@github/actions-workflow-parser";
|
||||
import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert";
|
||||
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
|
||||
import {TokenResult} from "./utils/find-token";
|
||||
import {File} from "@github/actions-workflow-parser/workflows/file";
|
||||
import {isString} from "@github/actions-workflow-parser/templates/tokens/type-guards";
|
||||
import {getCronDescription} from "@github/actions-workflow-parser/model/converter/cron";
|
||||
import {splitAllowedContext} from "@github/actions-workflow-parser/templates/allowed-context";
|
||||
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
|
||||
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
|
||||
import {isBasicExpression, isString} from "@github/actions-workflow-parser/templates/tokens/type-guards";
|
||||
import {File} from "@github/actions-workflow-parser/workflows/file";
|
||||
import {Position, TextDocument} from "vscode-languageserver-textdocument";
|
||||
import {Hover} from "vscode-languageserver-types";
|
||||
import {ContextProviderConfig} from "./context-providers/config";
|
||||
import {getContext, Mode} from "./context-providers/default";
|
||||
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
|
||||
import {ExpressionPos, mapToExpressionPos} from "./expression-hover/expression-pos";
|
||||
import {HoverVisitor} from "./expression-hover/visitor";
|
||||
import {validatorFunctions} from "./expression-validation/functions";
|
||||
import {info} from "./log";
|
||||
import {nullTrace} from "./nulltrace";
|
||||
import {findToken} from "./utils/find-token";
|
||||
import {findToken, TokenResult} from "./utils/find-token";
|
||||
import {mapRange} from "./utils/range";
|
||||
import {getCronDescription} from "@github/actions-workflow-parser/model/converter/cron";
|
||||
import {isStringExpression} from "./utils/type-guards";
|
||||
|
||||
export type HoverConfig = {
|
||||
descriptionProvider?: DescriptionProvider;
|
||||
contextProviderConfig?: ContextProviderConfig;
|
||||
};
|
||||
|
||||
export type DescriptionProvider = {
|
||||
getDescription(context: WorkflowContext, token: TemplateToken, path: TemplateToken[]): Promise<string | undefined>;
|
||||
};
|
||||
|
||||
export type HoverConfig = {
|
||||
descriptionProvider?: DescriptionProvider;
|
||||
};
|
||||
|
||||
// Render value description and Context when hovering over a key in a MappingToken
|
||||
export async function hover(document: TextDocument, position: Position, config?: HoverConfig): Promise<Hover | null> {
|
||||
const file: File = {
|
||||
name: document.uri,
|
||||
content: document.getText()
|
||||
};
|
||||
const result = parseWorkflow(file.name, [file], nullTrace);
|
||||
if (!result.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenResult = findToken(position, result.value);
|
||||
const token = tokenResult.token;
|
||||
|
||||
if (config?.contextProviderConfig && token && tokenResult.keyToken?.definition) {
|
||||
const isStringExpressionToken = isStringExpression(token);
|
||||
const isBasicExpressionToken = isBasicExpression(token);
|
||||
|
||||
if (isStringExpressionToken || isBasicExpressionToken) {
|
||||
info(`Calculating expression hover for token with definition ${tokenResult.keyToken.definition.key}`);
|
||||
|
||||
const allowedContext =
|
||||
token.definitionInfo?.allowedContext || tokenResult.keyToken?.definitionInfo?.allowedContext || [];
|
||||
const {namedContexts, functions} = splitAllowedContext(allowedContext);
|
||||
|
||||
const template = convertWorkflowTemplate(result.context, result.value, ErrorPolicy.TryConversion);
|
||||
const workflowContext = getWorkflowContext(document.uri, template, tokenResult.path);
|
||||
const context = await getContext(namedContexts, config.contextProviderConfig, workflowContext, Mode.Completion);
|
||||
|
||||
const exprPos = mapToExpressionPos(token, position);
|
||||
if (exprPos) {
|
||||
return expressionHover(exprPos, context, namedContexts, functions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!token?.definition) {
|
||||
return null;
|
||||
}
|
||||
@@ -40,7 +75,7 @@ export async function hover(document: TextDocument, position: Position, config?:
|
||||
|
||||
if (tokenResult.parent && isCronMappingValue(tokenResult)) {
|
||||
const tokenValue = (token as StringToken).value;
|
||||
let description = getCronDescription(tokenValue);
|
||||
const description = getCronDescription(tokenValue);
|
||||
if (description) {
|
||||
return {
|
||||
contents: description,
|
||||
@@ -88,3 +123,47 @@ function isCronMappingValue(tokenResult: TokenResult): boolean {
|
||||
tokenResult.token.value !== "cron"
|
||||
);
|
||||
}
|
||||
|
||||
function expressionHover(
|
||||
exprPos: ExpressionPos,
|
||||
context: DescriptionDictionary,
|
||||
namedContexts: string[],
|
||||
functions: FunctionInfo[]
|
||||
): Hover | null {
|
||||
const {expression, position, documentRange} = exprPos;
|
||||
|
||||
try {
|
||||
const l = new Lexer(expression);
|
||||
const lr = l.lex();
|
||||
|
||||
const p = new Parser(lr.tokens, namedContexts, functions);
|
||||
const expr = p.parse();
|
||||
|
||||
const v = new HoverVisitor(position, context, [], validatorFunctions);
|
||||
const hoverResult = v.hover(expr);
|
||||
if (!hoverResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mr = hoverResult.range;
|
||||
|
||||
return {
|
||||
contents: hoverResult?.description || hoverResult?.label,
|
||||
// Map the expression range back to a document range
|
||||
range: {
|
||||
start: {
|
||||
line: documentRange.start.line + mr.start.line,
|
||||
character: documentRange.start.character + mr.start.column
|
||||
},
|
||||
end: {
|
||||
line: documentRange.start.line + mr.end.line,
|
||||
character: documentRange.start.character + mr.end.column
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
// Hovering over an invalid expression should not cause an error here
|
||||
info(`Encountered error trying to calculate expression hover: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user