* Add missing validation for action.yml (parity with workflow files)
- Add uses format validation for composite action steps
- Validates owner/repo@ref format
- Supports docker:// and ./ local references
- Warns about shortened SHA refs (security concern)
- Detects reusable workflow references in wrong context
- Add if literal text detection for composite action steps
- Detects literal text outside ${{ }} that makes conditions always truthy
- Works for both plain string and mixed expression formats
- Uses shared hasFormatWithLiteralText() utility
- Add pre-if/post-if validation for node and docker actions
- Errors on explicit ${{ }} syntax (runner only supports implicit expressions)
- Literal text detection for implicit expressions
- New runs-if schema type with proper context (runner, github, job, env, inputs, status functions)
- Validates only in strict schema used by language services
- Add format() function validation for all expressions
- Validates format string syntax in all expression contexts
- Checks argument count matches placeholders
- Fix env and matrix context providers to return complete=false
- Prevents false positive 'unknown context' errors
- Matches behavior of other dynamic contexts (secrets, vars, etc.)
- Refactor validation utilities into utils/validate-uses.ts and utils/validate-if.ts
- Shared between workflow and action validation
- Consistent error messages and codes
* Add strategy and matrix contexts to runs-if definition
Based on runner source code analysis (actions/runner):
- ExecutionContext.InitializeJob() populates ExpressionValues from message.ContextData
- strategy and matrix are part of message.ContextData, available before any steps run
- StepsRunner evaluates all steps (pre, main, post) using the same code path
Did NOT add:
- steps: empty at pre-if time (no steps completed yet)
- hashFiles: workspace files don't exist at pre-step time
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
/**
|
|
* Shared validation utilities for `if` condition literal text detection.
|
|
* Used by both workflow and action validation.
|
|
*/
|
|
|
|
import {data} from "@actions/expressions";
|
|
import {Expr, FunctionCall, Literal, Logical} from "@actions/expressions/ast";
|
|
|
|
/**
|
|
* Checks if a format function contains literal text in its format string.
|
|
* This indicates user confusion about how expressions work.
|
|
*
|
|
* Example: format('push == {0}', github.event_name)
|
|
* The literal text "push == " will always evaluate to truthy.
|
|
*
|
|
* @param expr The expression to check
|
|
* @returns true if the expression is a format() call with literal text
|
|
*/
|
|
export function hasFormatWithLiteralText(expr: Expr): boolean {
|
|
// If this is a logical AND expression (from ensureStatusFunction wrapping)
|
|
// check the right side for the format call
|
|
if (expr instanceof Logical && expr.operator.lexeme === "&&" && expr.args.length === 2) {
|
|
return hasFormatWithLiteralText(expr.args[1]);
|
|
}
|
|
|
|
if (!(expr instanceof FunctionCall)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if this is a format function
|
|
if (expr.functionName.lexeme.toLowerCase() !== "format") {
|
|
return false;
|
|
}
|
|
|
|
// Check if the first argument is a string literal
|
|
if (expr.args.length < 1) {
|
|
return false;
|
|
}
|
|
|
|
const firstArg = expr.args[0];
|
|
if (!(firstArg instanceof Literal) || firstArg.literal.kind !== data.Kind.String) {
|
|
return false;
|
|
}
|
|
|
|
// Get the format string and trim whitespace
|
|
const formatString = firstArg.literal.coerceString();
|
|
const trimmed = formatString.trim();
|
|
|
|
// Check if there's literal text (non-replacement tokens) after trimming
|
|
let inToken = false;
|
|
for (let i = 0; i < trimmed.length; i++) {
|
|
if (!inToken && trimmed[i] === "{") {
|
|
inToken = true;
|
|
} else if (inToken && trimmed[i] === "}") {
|
|
inToken = false;
|
|
} else if (inToken && trimmed[i] >= "0" && trimmed[i] <= "9") {
|
|
// OK - this is a replacement token like {0}, {1}, etc.
|
|
} else {
|
|
// Found literal text
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|