@actions/languageservice 0.3.34 → 0.3.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"validate-action.d.ts","sourceRoot":"","sources":["../src/validate-action.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EAAC,YAAY,EAAC,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAC,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AAK3E,OAAO,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAiEjH"}
1
+ {"version":3,"file":"validate-action.d.ts","sourceRoot":"","sources":["../src/validate-action.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,EAAC,YAAY,EAAC,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAC,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AAK3E,OAAO,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AA2B/C;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAyEjH"}
@@ -4,6 +4,7 @@
4
4
  import { isMapping } from "@actions/workflow-parser";
5
5
  import { isActionStep } from "@actions/workflow-parser/model/type-guards";
6
6
  import { ErrorPolicy } from "@actions/workflow-parser/model/convert";
7
+ import { MappingToken } from "@actions/workflow-parser/templates/tokens/mapping-token";
7
8
  import { SequenceToken } from "@actions/workflow-parser/templates/tokens/sequence-token";
8
9
  import { TemplateToken } from "@actions/workflow-parser/templates/tokens/template-token";
9
10
  import { DiagnosticSeverity } from "vscode-languageserver-types";
@@ -11,6 +12,29 @@ import { error } from "./log.js";
11
12
  import { mapRange } from "./utils/range.js";
12
13
  import { getOrConvertActionTemplate, getOrParseAction } from "./utils/workflow-cache.js";
13
14
  import { validateActionReference } from "./validate-action-reference.js";
15
+ /**
16
+ * Valid keys for each action type under the `runs:` section.
17
+ * Source: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionManifestManager.cs
18
+ */
19
+ const NODE_KEYS = new Set(["using", "main", "pre", "post", "pre-if", "post-if"]);
20
+ const COMPOSITE_KEYS = new Set(["using", "steps"]);
21
+ const DOCKER_KEYS = new Set([
22
+ "using",
23
+ "image",
24
+ "args",
25
+ "env",
26
+ "entrypoint",
27
+ "pre-entrypoint",
28
+ "pre-if",
29
+ "post-entrypoint",
30
+ "post-if"
31
+ ]);
32
+ /**
33
+ * Required keys for each action type (besides 'using').
34
+ */
35
+ const NODE_REQUIRED_KEYS = ["main"];
36
+ const COMPOSITE_REQUIRED_KEYS = ["steps"];
37
+ const DOCKER_REQUIRED_KEYS = ["image"];
14
38
  /**
15
39
  * Validates an action.yml file
16
40
  *
@@ -30,8 +54,14 @@ export async function validateAction(textDocument, config) {
30
54
  if (!result) {
31
55
  return [];
32
56
  }
33
- // Map parser errors to diagnostics
34
- for (const err of result.context.errors.getErrors()) {
57
+ // Get schema errors
58
+ const schemaErrors = result.context.errors.getErrors();
59
+ // Run custom runs key validation, which also filters redundant schema errors in place
60
+ if (result.value) {
61
+ diagnostics.push(...validateRunsKeysAndFilterErrors(result.value, schemaErrors));
62
+ }
63
+ // Map remaining schema errors to diagnostics
64
+ for (const err of schemaErrors) {
35
65
  const range = mapRange(err.range);
36
66
  // Determine severity based on error type
37
67
  let severity = DiagnosticSeverity.Error;
@@ -86,4 +116,117 @@ function findStepsSequence(root) {
86
116
  }
87
117
  return undefined;
88
118
  }
119
+ /**
120
+ * Validates that the keys under `runs:` are valid for the specified `using:` type.
121
+ * Also filters out schema errors (in place) that this validation replaces with more specific messages.
122
+ */
123
+ function validateRunsKeysAndFilterErrors(root, schemaErrors // mutated: redundant errors are removed
124
+ ) {
125
+ const diagnostics = [];
126
+ // Find the runs mapping from the root
127
+ let runsMapping;
128
+ if (root instanceof MappingToken) {
129
+ for (let i = 0; i < root.count; i++) {
130
+ const { key, value } = root.get(i);
131
+ if (key.toString().toLowerCase() === "runs" && value instanceof MappingToken) {
132
+ runsMapping = value;
133
+ break;
134
+ }
135
+ }
136
+ }
137
+ if (!runsMapping) {
138
+ return diagnostics;
139
+ }
140
+ // Get the using value from the runs mapping
141
+ let usingValue;
142
+ for (let i = 0; i < runsMapping.count; i++) {
143
+ const { key, value } = runsMapping.get(i);
144
+ if (key.toString().toLowerCase() === "using") {
145
+ usingValue = value.toString();
146
+ break;
147
+ }
148
+ }
149
+ if (!usingValue) {
150
+ return diagnostics; // No using value, let schema validation handle it
151
+ }
152
+ // Determine allowed keys, required keys, and action type name
153
+ let allowedKeys;
154
+ let requiredKeys;
155
+ let actionType;
156
+ if (usingValue.match(/^node\d+$/i)) {
157
+ allowedKeys = NODE_KEYS;
158
+ requiredKeys = NODE_REQUIRED_KEYS;
159
+ actionType = "Node.js";
160
+ }
161
+ else if (usingValue.toLowerCase() === "composite") {
162
+ allowedKeys = COMPOSITE_KEYS;
163
+ requiredKeys = COMPOSITE_REQUIRED_KEYS;
164
+ actionType = "composite";
165
+ }
166
+ else if (usingValue.toLowerCase() === "docker") {
167
+ allowedKeys = DOCKER_KEYS;
168
+ requiredKeys = DOCKER_REQUIRED_KEYS;
169
+ actionType = "Docker";
170
+ }
171
+ else {
172
+ return diagnostics; // Unknown type, let schema validation handle it
173
+ }
174
+ // Get all present keys
175
+ const presentKeys = new Set();
176
+ for (let i = 0; i < runsMapping.count; i++) {
177
+ const { key } = runsMapping.get(i);
178
+ presentKeys.add(key.toString().toLowerCase());
179
+ }
180
+ // Check for invalid keys
181
+ for (let i = 0; i < runsMapping.count; i++) {
182
+ const { key } = runsMapping.get(i);
183
+ const keyStr = key.toString().toLowerCase();
184
+ if (!allowedKeys.has(keyStr)) {
185
+ diagnostics.push({
186
+ severity: DiagnosticSeverity.Error,
187
+ range: mapRange(key.range),
188
+ message: `'${key.toString()}' is not valid for ${actionType} actions (using: ${usingValue})`
189
+ });
190
+ }
191
+ }
192
+ // Check for missing required keys
193
+ for (const requiredKey of requiredKeys) {
194
+ if (!presentKeys.has(requiredKey)) {
195
+ // Find the 'using' key to report the error location
196
+ let usingKeyRange = runsMapping.range;
197
+ for (let i = 0; i < runsMapping.count; i++) {
198
+ const { key } = runsMapping.get(i);
199
+ if (key.toString().toLowerCase() === "using") {
200
+ usingKeyRange = key.range;
201
+ break;
202
+ }
203
+ }
204
+ diagnostics.push({
205
+ severity: DiagnosticSeverity.Error,
206
+ range: mapRange(usingKeyRange),
207
+ message: `'${requiredKey}' is required for ${actionType} actions (using: ${usingValue})`
208
+ });
209
+ }
210
+ }
211
+ // Remove schema errors that we're replacing with more specific messages (mutate in place)
212
+ for (let i = schemaErrors.length - 1; i >= 0; i--) {
213
+ const err = schemaErrors[i];
214
+ // Keep errors not at the runs section start
215
+ if (err.range?.start.line !== runsMapping.range?.start.line ||
216
+ err.range?.start.column !== runsMapping.range?.start.column) {
217
+ continue;
218
+ }
219
+ // Check if this is an error we're replacing
220
+ const isOneOfAmbiguity = err.rawMessage.startsWith("There's not enough info to determine");
221
+ const isRequiredKey = /^Required property is missing: (main|steps|image)$/.test(err.rawMessage);
222
+ if (!isOneOfAmbiguity && !isRequiredKey) {
223
+ continue; // Keep errors we're not replacing
224
+ }
225
+ // Remove only if we have custom diagnostics for this
226
+ if (diagnostics.length > 0) {
227
+ schemaErrors.splice(i, 1);
228
+ }
229
+ }
230
+ return diagnostics;
231
+ }
89
232
  //# sourceMappingURL=validate-action.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-action.js","sourceRoot":"","sources":["../src/validate-action.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validate-action.js","sourceRoot":"","sources":["../src/validate-action.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Format string validation for format() function calls.
3
+ * Port of Go's format_validator.go from actions-workflow-parser.
4
+ */
5
+ import { Expr } from "@actions/expressions/ast";
6
+ /**
7
+ * Error types for format string validation
8
+ */
9
+ export type FormatStringError = {
10
+ type: "invalid-syntax";
11
+ message: string;
12
+ } | {
13
+ type: "arg-count-mismatch";
14
+ expected: number;
15
+ provided: number;
16
+ };
17
+ /**
18
+ * Validates a format string and returns the maximum placeholder index.
19
+ * Port of Go's validateFormatString from format_validator.go.
20
+ *
21
+ * @param formatString The format string to validate
22
+ * @returns { valid: boolean, maxArgIndex: number } where maxArgIndex is -1 if no placeholders
23
+ */
24
+ export declare function validateFormatString(formatString: string): {
25
+ valid: boolean;
26
+ maxArgIndex: number;
27
+ };
28
+ /**
29
+ * Walks an expression AST to find and validate all format() function calls.
30
+ *
31
+ * @param expr The expression AST to validate
32
+ * @returns Array of validation errors found
33
+ */
34
+ export declare function validateFormatCalls(expr: Expr): FormatStringError[];
35
+ //# sourceMappingURL=validate-format-string.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-format-string.d.ts","sourceRoot":"","sources":["../src/validate-format-string.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAC,IAAI,EAAuE,MAAM,0BAA0B,CAAC;AAGpH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACzC;IAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAAC;AAErE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAyFhG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,iBAAiB,EAAE,CAsCnE"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Format string validation for format() function calls.
3
+ * Port of Go's format_validator.go from actions-workflow-parser.
4
+ */
5
+ import { FunctionCall, Literal, Binary, Unary, Logical, Grouping, IndexAccess } from "@actions/expressions/ast";
6
+ import { Kind } from "@actions/expressions/data/expressiondata";
7
+ /**
8
+ * Validates a format string and returns the maximum placeholder index.
9
+ * Port of Go's validateFormatString from format_validator.go.
10
+ *
11
+ * @param formatString The format string to validate
12
+ * @returns { valid: boolean, maxArgIndex: number } where maxArgIndex is -1 if no placeholders
13
+ */
14
+ export function validateFormatString(formatString) {
15
+ let maxIndex = -1;
16
+ let i = 0;
17
+ while (i < formatString.length) {
18
+ // Find next left brace
19
+ let lbrace = -1;
20
+ for (let j = i; j < formatString.length; j++) {
21
+ if (formatString[j] === "{") {
22
+ lbrace = j;
23
+ break;
24
+ }
25
+ }
26
+ // Find next right brace
27
+ let rbrace = -1;
28
+ for (let j = i; j < formatString.length; j++) {
29
+ if (formatString[j] === "}") {
30
+ rbrace = j;
31
+ break;
32
+ }
33
+ }
34
+ // No more braces
35
+ if (lbrace < 0 && rbrace < 0) {
36
+ break;
37
+ }
38
+ // Left brace comes first (or only left brace exists)
39
+ if (lbrace >= 0 && (rbrace < 0 || lbrace < rbrace)) {
40
+ // Check if it's escaped
41
+ if (lbrace + 1 < formatString.length && formatString[lbrace + 1] === "{") {
42
+ // Escaped left brace
43
+ i = lbrace + 2;
44
+ continue;
45
+ }
46
+ // This is a placeholder opening - find the closing brace
47
+ rbrace = -1;
48
+ for (let j = lbrace + 1; j < formatString.length; j++) {
49
+ if (formatString[j] === "}") {
50
+ rbrace = j;
51
+ break;
52
+ }
53
+ }
54
+ if (rbrace < 0) {
55
+ // Missing closing brace
56
+ return { valid: false, maxArgIndex: -1 };
57
+ }
58
+ // Validate placeholder content (must be digits only)
59
+ if (rbrace === lbrace + 1) {
60
+ // Empty placeholder {}
61
+ return { valid: false, maxArgIndex: -1 };
62
+ }
63
+ // Parse the index and validate it's all digits
64
+ let index = 0;
65
+ for (let j = lbrace + 1; j < rbrace; j++) {
66
+ const c = formatString[j];
67
+ if (c < "0" || c > "9") {
68
+ // Non-numeric character
69
+ return { valid: false, maxArgIndex: -1 };
70
+ }
71
+ index = index * 10 + (c.charCodeAt(0) - "0".charCodeAt(0));
72
+ }
73
+ if (index > maxIndex) {
74
+ maxIndex = index;
75
+ }
76
+ i = rbrace + 1;
77
+ continue;
78
+ }
79
+ // Right brace comes first (or only right brace exists)
80
+ // Check if it's escaped
81
+ if (rbrace + 1 < formatString.length && formatString[rbrace + 1] === "}") {
82
+ // Escaped right brace
83
+ i = rbrace + 2;
84
+ continue;
85
+ }
86
+ // Unescaped right brace outside of placeholder
87
+ return { valid: false, maxArgIndex: -1 };
88
+ }
89
+ return { valid: true, maxArgIndex: maxIndex };
90
+ }
91
+ /**
92
+ * Walks an expression AST to find and validate all format() function calls.
93
+ *
94
+ * @param expr The expression AST to validate
95
+ * @returns Array of validation errors found
96
+ */
97
+ export function validateFormatCalls(expr) {
98
+ const errors = [];
99
+ const stack = [expr];
100
+ while (stack.length > 0) {
101
+ const node = stack.pop();
102
+ if (!node) {
103
+ continue;
104
+ }
105
+ if (node instanceof FunctionCall) {
106
+ if (node.functionName.lexeme.toLowerCase() === "format") {
107
+ const error = validateSingleFormatCall(node);
108
+ if (error) {
109
+ errors.push(error);
110
+ }
111
+ }
112
+ // Push args for further processing (to find nested format calls)
113
+ for (const arg of node.args) {
114
+ stack.push(arg);
115
+ }
116
+ }
117
+ else if (node instanceof Binary) {
118
+ stack.push(node.left, node.right);
119
+ }
120
+ else if (node instanceof Unary) {
121
+ stack.push(node.expr);
122
+ }
123
+ else if (node instanceof Logical) {
124
+ for (const arg of node.args) {
125
+ stack.push(arg);
126
+ }
127
+ }
128
+ else if (node instanceof Grouping) {
129
+ stack.push(node.group);
130
+ }
131
+ else if (node instanceof IndexAccess) {
132
+ stack.push(node.expr, node.index);
133
+ }
134
+ // Literal, ContextAccess - no children to process
135
+ }
136
+ return errors;
137
+ }
138
+ /**
139
+ * Validates a single format() function call.
140
+ *
141
+ * @param fc The FunctionCall AST node
142
+ * @returns Validation error if found, undefined if valid
143
+ */
144
+ function validateSingleFormatCall(fc) {
145
+ // Must have at least one argument (the format string)
146
+ if (fc.args.length < 1) {
147
+ return undefined;
148
+ }
149
+ // First argument must be a string literal
150
+ const firstArg = fc.args[0];
151
+ if (!(firstArg instanceof Literal) || firstArg.literal.kind !== Kind.String) {
152
+ return undefined; // Can't validate dynamic format strings
153
+ }
154
+ const formatString = firstArg.literal.coerceString();
155
+ const numArgs = fc.args.length - 1; // Subtract 1 for format string itself
156
+ const { valid, maxArgIndex } = validateFormatString(formatString);
157
+ if (!valid) {
158
+ return {
159
+ type: "invalid-syntax",
160
+ message: "Format string has invalid syntax (missing closing brace, unescaped braces, or invalid placeholder)"
161
+ };
162
+ }
163
+ if (maxArgIndex >= numArgs) {
164
+ return {
165
+ type: "arg-count-mismatch",
166
+ expected: maxArgIndex + 1,
167
+ provided: numArgs
168
+ };
169
+ }
170
+ return undefined;
171
+ }
172
+ //# sourceMappingURL=validate-format-string.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-format-string.js","sourceRoot":"","sources":["../src/validate-format-string.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { FeatureFlags } from "@actions/expressions";
1
2
  import { FileProvider } from "@actions/workflow-parser/workflows/file-provider";
2
3
  import { TextDocument } from "vscode-languageserver-textdocument";
3
4
  import { Diagnostic } from "vscode-languageserver-types";
@@ -9,6 +10,7 @@ export type ValidationConfig = {
9
10
  contextProviderConfig?: ContextProviderConfig;
10
11
  actionsMetadataProvider?: ActionsMetadataProvider;
11
12
  fileProvider?: FileProvider;
13
+ featureFlags?: FeatureFlags;
12
14
  };
13
15
  export type ActionsMetadataProvider = {
14
16
  fetchActionMetadata(action: ActionReference): Promise<ActionMetadata | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAYA,OAAO,EAAC,YAAY,EAAC,MAAM,kDAAkD,CAAC;AAC9E,OAAO,EAAC,YAAY,EAAC,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAC,UAAU,EAA0B,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAC,qBAAqB,EAAC,MAAM,+BAA+B,CAAC;AAapE,OAAO,EAAC,mBAAmB,EAAoB,MAAM,6BAA6B,CAAC;AAMnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;IAClD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CACnF,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAI3G"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAsB,MAAM,sBAAsB,CAAC;AAYvE,OAAO,EAAC,YAAY,EAAC,MAAM,kDAAkD,CAAC;AAC9E,OAAO,EAAC,YAAY,EAAC,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAC,UAAU,EAA0B,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAC,qBAAqB,EAAC,MAAM,+BAA+B,CAAC;AAcpE,OAAO,EAAC,mBAAmB,EAAoB,MAAM,6BAA6B,CAAC;AAMnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;IAClD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CACnF,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAI3G"}
package/dist/validate.js CHANGED
@@ -20,6 +20,7 @@ import { mapRange } from "./utils/range.js";
20
20
  import { getOrConvertWorkflowTemplate, getOrParseWorkflow } from "./utils/workflow-cache.js";
21
21
  import { validateActionReference } from "./validate-action-reference.js";
22
22
  import { validateAction } from "./validate-action.js";
23
+ import { validateFormatCalls } from "./validate-format-string.js";
23
24
  import { ValueProviderKind } from "./value-providers/config.js";
24
25
  import { defaultValueProviders } from "./value-providers/default.js";
25
26
  const CRON_SCHEDULE_DOCS_URL = "https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#onschedule";
@@ -58,7 +59,7 @@ async function validateWorkflow(textDocument, config) {
58
59
  errorPolicy: ErrorPolicy.TryConversion
59
60
  });
60
61
  // Validate expressions and value providers
61
- await additionalValidations(diagnostics, textDocument.uri, template, result.value, config);
62
+ await additionalValidations(diagnostics, textDocument.uri, template, result.value, config, config?.featureFlags);
62
63
  }
63
64
  // For now map parser errors directly to diagnostics
64
65
  for (const error of result.context.errors.getErrors()) {
@@ -74,8 +75,8 @@ async function validateWorkflow(textDocument, config) {
74
75
  }
75
76
  return diagnostics;
76
77
  }
77
- async function additionalValidations(diagnostics, documentUri, template, root, config) {
78
- for (const [parent, token, key] of TemplateToken.traverse(root)) {
78
+ async function additionalValidations(diagnostics, documentUri, template, root, config, featureFlags) {
79
+ for (const [parent, token, key, ancestors] of TemplateToken.traverse(root)) {
79
80
  // If the token is a value in a pair, use the key definition for validation
80
81
  // If the token has a parent (map, sequence, etc), use this definition for validation
81
82
  const validationToken = key || parent || token;
@@ -84,7 +85,11 @@ async function additionalValidations(diagnostics, documentUri, template, root, c
84
85
  if (isBasicExpression(token) && token.range) {
85
86
  await validateExpression(diagnostics, token, validationToken.definitionInfo?.allowedContext || [], config?.contextProviderConfig, getProviderContext(documentUri, template, root, token.range), key?.definition?.key);
86
87
  }
87
- // If this is a job-if, step-if, or snapshot-if field (which are strings that should be treated as expressions), validate it
88
+ // Validate block scalar chomping for expressions and strings
89
+ if (featureFlags?.isEnabled("blockScalarChompingWarning")) {
90
+ validateBlockScalarChomping(diagnostics, token, parent, key, ancestors);
91
+ }
92
+ // `if` conditions allow omitting ${{ }}, so validate strings in these fields as expressions
88
93
  const definitionKey = token.definition?.key;
89
94
  if (isString(token) &&
90
95
  token.range &&
@@ -95,7 +100,7 @@ async function additionalValidations(diagnostics, documentUri, template, root, c
95
100
  // Ensure the condition has a status function, wrapping if needed
96
101
  const finalCondition = ensureStatusFunction(condition, token.definitionInfo);
97
102
  // Create a BasicExpressionToken for validation
98
- const expressionToken = new BasicExpressionToken(token.file, token.range, finalCondition, token.definitionInfo, undefined, token.source);
103
+ const expressionToken = new BasicExpressionToken(token.file, token.range, finalCondition, token.definitionInfo, undefined, token.source, undefined, token.blockScalarHeader);
99
104
  await validateExpression(diagnostics, expressionToken, validationToken.definitionInfo?.allowedContext || [], config?.contextProviderConfig, getProviderContext(documentUri, template, root, token.range));
100
105
  }
101
106
  }
@@ -562,6 +567,26 @@ async function validateExpression(diagnostics, token, allowedContext, contextPro
562
567
  // Ignore any error here, we should've caught this earlier in the parsing process
563
568
  continue;
564
569
  }
570
+ // Validate format() function calls
571
+ const formatErrors = validateFormatCalls(expr);
572
+ for (const formatError of formatErrors) {
573
+ if (formatError.type === "invalid-syntax") {
574
+ diagnostics.push({
575
+ message: `Invalid format string: ${formatError.message}`,
576
+ range: mapRange(expression.range),
577
+ severity: DiagnosticSeverity.Error,
578
+ code: "invalid-format-string"
579
+ });
580
+ }
581
+ else if (formatError.type === "arg-count-mismatch") {
582
+ diagnostics.push({
583
+ message: `Format string references argument {${formatError.expected - 1}} but only ${formatError.provided} argument(s) provided`,
584
+ range: mapRange(expression.range),
585
+ severity: DiagnosticSeverity.Error,
586
+ code: "format-arg-count-mismatch"
587
+ });
588
+ }
589
+ }
565
590
  const context = await getWorkflowExpressionContext(namedContexts, contextProviderConfig, workflowContext, Mode.Validation);
566
591
  const e = new ValidationEvaluator(expr, wrapDictionary(context), validatorFunctions);
567
592
  e.validate();
@@ -631,4 +656,78 @@ function getStaticConcurrencyGroup(token) {
631
656
  }
632
657
  return undefined;
633
658
  }
659
+ /**
660
+ * Validates YAML block scalar chomping.
661
+ *
662
+ * Block scalars (| and >) implicitly add a trailing newline by default ("clip" chomping).
663
+ * This is often unintended by the workflow author and can cause unexpected behavior.
664
+ * This function warns on certain fields when clip chomping is used (implicit trailing newline)
665
+ * and suggests they explicitly use strip (|-) or keep (|+) to clarify intent.
666
+ *
667
+ * Only specific fields are validated - those where trailing newlines may cause
668
+ * issues but aren't automatically trimmed server-side. For example env, inputs, outputs, etc.
669
+ *
670
+ * Skipped fields:
671
+ * - run: Multi-line scripts commonly have trailing newlines
672
+ * - Fields trimmed server-side: name, uses, shell, if, etc.
673
+ */
674
+ function validateBlockScalarChomping(diagnostics, token, parent, key, ancestors) {
675
+ // Not an expression or string?
676
+ if (!isBasicExpression(token) && !isString(token)) {
677
+ return;
678
+ }
679
+ // Not a block scalar?
680
+ const header = token.blockScalarHeader;
681
+ if (!header) {
682
+ return;
683
+ }
684
+ // Not "clip" chomp style?
685
+ if (header.includes("+") || header.includes("-")) {
686
+ return;
687
+ }
688
+ // Check if we should warn
689
+ let shouldWarn = false;
690
+ const parentDefinitionName = parent?.definition?.key;
691
+ const tokenDefinitionName = token.definition?.key;
692
+ const keyName = key && isString(key) ? key.value : undefined;
693
+ if (parentDefinitionName &&
694
+ [
695
+ "workflow-env",
696
+ "job-env",
697
+ "step-env",
698
+ "container-env",
699
+ "step-with",
700
+ "job-outputs",
701
+ "workflow-job-with",
702
+ "workflow-job-secrets"
703
+ ].includes(parentDefinitionName)) {
704
+ // env, with, outputs, or secrets fields
705
+ shouldWarn = true;
706
+ }
707
+ else if (ancestors.some(ancestor => {
708
+ const ancestorKey = ancestor.definition?.key;
709
+ return ancestorKey === "matrix" || ancestorKey === "matrix-filter" || ancestorKey === "matrix-filter-item";
710
+ })) {
711
+ // Matrix values (vectors, include, exclude)
712
+ shouldWarn = true;
713
+ }
714
+ else if (tokenDefinitionName && ["workflow-concurrency", "job-concurrency"].includes(tokenDefinitionName)) {
715
+ // Concurrency shorthand
716
+ shouldWarn = true;
717
+ }
718
+ else if (keyName === "group" && parentDefinitionName === "concurrency-mapping") {
719
+ // Concurrency group field
720
+ shouldWarn = true;
721
+ }
722
+ if (!shouldWarn) {
723
+ return;
724
+ }
725
+ const blockIndicator = header.startsWith("|") ? "|" : ">";
726
+ diagnostics.push({
727
+ message: `Block scalar '${blockIndicator}' implicitly adds a trailing newline that may be unintentional. Use '${blockIndicator}-' to remove it, or '${blockIndicator}+' to explicitly keep it.`,
728
+ range: mapRange(token.range),
729
+ severity: DiagnosticSeverity.Warning,
730
+ code: "block-scalar-chomping"
731
+ });
732
+ }
634
733
  //# sourceMappingURL=validate.js.map