Files
languageservices/workflow-parser/src/templates/tokens/basic-expression-token.ts
T
eric sciple 2816233a40 Add block scalar newline warning (#295)
In YAML, block scalars (`|` and `>`) silently add a trailing newline by default
("clip" chomping). This can cause subtle bugs when the newline is unintentional.

This PR adds a warning when clip chomping is used in fields where trailing
newlines commonly cause issues:

- Environment variables (workflow, job, step, container, service levels)
- Action inputs (`with:`)
- Reusable workflow inputs and secrets
- Job outputs
- Matrix values (including `include` and `exclude`)
- Concurrency groups

The warning suggests using `|-` (strip) or `|+` (keep) to be explicit.

Intentionally does NOT warn for:
- `run:` scripts (trailing newlines are normal)
- Fields trimmed server-side (`if:`, `name:`, `runs-on:`, etc.)

The feature is gated behind the `blockScalarChompingWarning` feature flag.
2026-01-12 09:36:43 -06:00

103 lines
3.6 KiB
TypeScript

import {DefinitionInfo} from "../schema/definition-info.js";
import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "../template-constants.js";
import {ExpressionToken} from "./expression-token.js";
import {ScalarToken} from "./scalar-token.js";
import {SerializedExpressionToken} from "./serialization.js";
import {TemplateToken} from "./template-token.js";
import {TokenRange} from "./token-range.js";
import {TokenType} from "./types.js";
export class BasicExpressionToken extends ExpressionToken {
private readonly expr: string;
public readonly source: string | undefined;
public readonly originalExpressions: BasicExpressionToken[] | undefined;
/**
* The range of the expression within the source string.
*
* `range` is the range of the entire expression, including the `${{` and `}}`. `expression` is only the expression
* without any ${{ }} markers. `expressionRange` is the range of just the expression within the document.
*/
public readonly expressionRange: TokenRange | undefined;
/**
* The block scalar header (e.g., "|", "|-", "|+", ">", ">-", ">+") if parsed from a YAML block scalar.
*/
public readonly blockScalarHeader: string | undefined;
/**
* @param file The file ID where this token originated
* @param range The range of the entire expression including `${{` and `}}`
* @param expression The expression string without `${{` and `}}` markers
* @param definitionInfo Schema definition info for this token
* @param originalExpressions If transformed from individual expressions (e.g., format()), these are the originals
* @param source The original source string from the YAML
* @param expressionRange The range of just the expression, excluding `${{` and `}}`
* @param blockScalarHeader The block scalar header (e.g., "|", "|-") if parsed from a YAML block scalar
*/
public constructor(
file: number | undefined,
range: TokenRange | undefined,
expression: string,
definitionInfo: DefinitionInfo | undefined,
originalExpressions: BasicExpressionToken[] | undefined,
source: string | undefined,
expressionRange?: TokenRange | undefined,
blockScalarHeader?: string | undefined
) {
super(TokenType.BasicExpression, file, range, undefined, definitionInfo);
this.expr = expression;
this.source = source;
this.originalExpressions = originalExpressions;
this.expressionRange = expressionRange;
this.blockScalarHeader = blockScalarHeader;
}
public get expression(): string {
return this.expr;
}
public override clone(omitSource?: boolean): TemplateToken {
return omitSource
? new BasicExpressionToken(
undefined,
undefined,
this.expr,
this.definitionInfo,
this.originalExpressions,
this.source,
this.expressionRange,
this.blockScalarHeader
)
: new BasicExpressionToken(
this.file,
this.range,
this.expr,
this.definitionInfo,
this.originalExpressions,
this.source,
this.expressionRange,
this.blockScalarHeader
);
}
public override toString(): string {
return `${OPEN_EXPRESSION} ${this.expr} ${CLOSE_EXPRESSION}`;
}
public override toDisplayString(): string {
// TODO: Implement expression display string to match `BasicExpressionToken#ToDisplayString()` in the C# parser
return ScalarToken.trimDisplayString(this.toString());
}
public override toJSON(): SerializedExpressionToken {
return {
type: TokenType.BasicExpression,
expr: this.expr
};
}
}