d2ffb50a92
- Add validation, completion, hover, and document links for action.yml files - Implement document type detection to route action.yml to action-specific handlers - Add expression context for composite actions (inputs, steps, github, runner, etc.) - Add schema validation for required fields, branding, and composite step requirements - Support JavaScript (node20/node24), Docker, and composite action types - Validate action references in composite action uses steps - Add JSDoc comments to parser and template functions - Refactor hover to use hoverToken consistently - Fix lint errors and add return type annotations
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import {isString} from "@actions/workflow-parser";
|
|
import {StringToken} from "@actions/workflow-parser/templates/tokens/string-token";
|
|
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
|
|
import {Position, TextDocument} from "vscode-languageserver-textdocument";
|
|
import {Range} from "vscode-languageserver-types";
|
|
|
|
const PLACEHOLDER_KEY = "key";
|
|
|
|
/**
|
|
* Transforms a document to make it valid YAML so the parser can understand
|
|
* the cursor position during auto-completion.
|
|
*
|
|
* When typing in an IDE, the document is usually invalid YAML:
|
|
* - `runs-on` without `:` isn't a valid key
|
|
* - Empty lines don't parse as anything
|
|
* - `- ` without a value isn't complete
|
|
*
|
|
* This function inserts placeholders to make the document parseable:
|
|
* - Empty line → inserts `key:` placeholder
|
|
* - Line without colon → appends `:`
|
|
* - Sequence item `- ` → inserts `key` after the dash
|
|
*
|
|
* Lines containing `${{` are skipped to avoid breaking multi-line strings.
|
|
*
|
|
* The `isPlaceholder()` helper filters out the fake entries from completions.
|
|
*/
|
|
export function transform(doc: TextDocument, pos: Position): [TextDocument, Position] {
|
|
let offset = doc.offsetAt(pos);
|
|
|
|
const lineRange: Range = {
|
|
start: {line: pos.line, character: 0},
|
|
end: {line: pos.line, character: Number.MAX_SAFE_INTEGER}
|
|
};
|
|
|
|
let line = doc.getText(lineRange);
|
|
|
|
// If the line includes a new-line char, strip that out
|
|
const newLinePos = line.indexOf("\n");
|
|
if (newLinePos >= 0) {
|
|
line = line.substring(0, newLinePos);
|
|
}
|
|
lineRange.end.character = line.length;
|
|
|
|
const linePos = pos.character;
|
|
|
|
// Special case for Actions, if this line contains an expression marker, do _not_ transform. This is
|
|
// an ugly fix for auto-completion in multi-line YAML strings. At this point in the process, we cannot
|
|
// determine if a line is in such a multi-line string.
|
|
if (line.indexOf("${{") !== -1) {
|
|
return [doc, pos];
|
|
}
|
|
|
|
const containsColon = line.indexOf(":") !== -1;
|
|
if (!containsColon) {
|
|
const trimmedLine = line.trim();
|
|
if (trimmedLine === "" || trimmedLine === "-") {
|
|
// Pos in sequence or empty line
|
|
let spacer = "";
|
|
if (trimmedLine === "-" && !line.endsWith(" ")) {
|
|
spacer = " ";
|
|
offset++;
|
|
}
|
|
|
|
line =
|
|
line.substring(0, linePos) +
|
|
spacer +
|
|
PLACEHOLDER_KEY +
|
|
(trimmedLine === "-" ? "" : ":") +
|
|
line.substring(linePos);
|
|
|
|
// Adjust pos by one to prevent a sequence node being marked as active
|
|
offset++;
|
|
} else if (!trimmedLine.startsWith("-")) {
|
|
// Add `:` to end of line
|
|
line = line + ":";
|
|
}
|
|
}
|
|
|
|
const newDoc = TextDocument.create(doc.uri, doc.languageId, doc.version, doc.getText());
|
|
|
|
TextDocument.update(
|
|
newDoc,
|
|
[
|
|
{
|
|
range: lineRange,
|
|
text: line
|
|
}
|
|
],
|
|
newDoc.version + 1
|
|
);
|
|
|
|
return [newDoc, newDoc.positionAt(offset)];
|
|
}
|
|
|
|
// Detect placeholder key and value added by transform
|
|
export function isPlaceholder(key: StringToken, value: TemplateToken) {
|
|
if (key.value === PLACEHOLDER_KEY && isString(value) && value.value == "") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|