Compare commits

...

6 Commits

Author SHA1 Message Date
eric sciple 667b1273e1 Validate expression block-scalar chomping 2025-11-22 05:43:40 +00:00
eric sciple 7f8bba4305 Merge pull request #214 from actions/release/0.3.20
Release version 0.3.20
2025-11-19 10:34:20 -06:00
GitHub Actions 43feb1a1f4 Release extension version 0.3.20 2025-11-19 16:32:52 +00:00
eric sciple d4aeaa3f3f Merge pull request #213 from indigok/patch-1
Add new artifact-metadata permission to schema
2025-11-19 10:19:40 -06:00
Indigo e4f8f24be3 Closing bracket
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 14:40:37 -08:00
Indigo 168cf44245 Add new artifact-metadata permission to schema 2025-11-13 13:54:34 -08:00
19 changed files with 2946 additions and 29 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/expressions",
"version": "0.3.19",
"version": "0.3.20",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageserver",
"version": "0.3.19",
"version": "0.3.20",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -43,8 +43,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/languageservice": "^0.3.19",
"@actions/workflow-parser": "^0.3.19",
"@actions/languageservice": "^0.3.20",
"@actions/workflow-parser": "^0.3.20",
"@octokit/rest": "^21.1.1",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageservice",
"version": "0.3.19",
"version": "0.3.20",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -44,8 +44,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.19",
"@actions/workflow-parser": "^0.3.19",
"@actions/expressions": "^0.3.20",
"@actions/workflow-parser": "^0.3.20",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
@@ -0,0 +1,58 @@
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - allowed cases", () => {
it("does NOT warn for step.run with clip chomping (exception)", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo \${{ github.event_name }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn for inline expression", async () => {
const input = `
on: push
jobs:
build:
if: \${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn for quoted string", async () => {
const input = `
on: push
jobs:
build:
if: "\${{ github.event_name == 'push' }}"
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
@@ -0,0 +1,142 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - boolean fields", () => {
describe("job continue-on-error", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
continue-on-error: |
\${{ matrix.experimental }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
continue-on-error: |+
\${{ matrix.experimental }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
continue-on-error: |-
\${{ matrix.experimental }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("step continue-on-error", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
continue-on-error: |
\${{ matrix.experimental }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
continue-on-error: |+
\${{ matrix.experimental }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
continue-on-error: |-
\${{ matrix.experimental }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
@@ -0,0 +1,188 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - if fields", () => {
describe("job-if", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
if: |
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
if: |+
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
if: |-
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("errors without ${{ }} (isExpression)", async () => {
const input = `
on: push
jobs:
build:
if: |
github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("uses > indicator in error message for folded scalars", async () => {
const input = `
on: push
jobs:
build:
if: >
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '>-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
});
describe("step-if", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- if: |
\${{ github.event_name == 'push' }}
run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- if: |+
\${{ github.event_name == 'push' }}
run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- if: |-
\${{ github.event_name == 'push' }}
run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
@@ -0,0 +1,428 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - number fields", () => {
describe("job timeout-minutes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: |
\${{ matrix.timeout }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: |+
\${{ matrix.timeout }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: |-
\${{ matrix.timeout }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("container.ports", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
ports: |
\${{ fromJSON('[80, 443]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
ports: |+
\${{ fromJSON('[8080, 9090]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
ports: |-
\${{ fromJSON('[80, 443]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("container.volumes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
volumes: |
\${{ fromJSON('["/data:/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
volumes: |+
\${{ fromJSON('["/data:/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
volumes: |-
\${{ fromJSON('["/data:/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("services.ports", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports: |
\${{ fromJSON('[5432]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports: |+
\${{ fromJSON('[5432]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports: |-
\${{ fromJSON('[5432]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("services.volumes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
volumes: |
\${{ fromJSON('["/var/lib/postgresql/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
volumes: |+
\${{ fromJSON('["/var/lib/postgresql/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
volumes: |-
\${{ fromJSON('["/var/lib/postgresql/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("step timeout-minutes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
timeout-minutes: |
\${{ matrix.timeout }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
timeout-minutes: |+
\${{ matrix.timeout }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
timeout-minutes: |-
\${{ matrix.timeout }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("expression validation", () => {
describe("block scalar chomping - fields that warn only for clip", () => {
describe("env", () => {
it("warns with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: |
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '|-' to strip or '|+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
it("does not warn with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: |+
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: |-
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("uses > indicator in warning message for folded scalars", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: >
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '>-' to strip or '>+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
});
describe("action input", () => {
it("warns with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: |
\${{ github.ref }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '|-' to strip or '|+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
it("does not warn with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: |+
\${{ github.ref }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: |-
\${{ github.ref }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("matrix value", () => {
it("warns with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: |
\${{ fromJSON('[1, 2, 3]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '|-' to strip or '|+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
it("does not warn with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: |+
\${{ fromJSON('[1, 2, 3]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: |-
\${{ fromJSON('[1, 2, 3]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
});
+99
View File
@@ -3,12 +3,14 @@ import {Expr} from "@actions/expressions/ast";
import {ParseWorkflowResult, WorkflowTemplate, isBasicExpression, isString} from "@actions/workflow-parser";
import {ErrorPolicy} from "@actions/workflow-parser/model/convert";
import {splitAllowedContext} from "@actions/workflow-parser/templates/allowed-context";
import {Definition} from "@actions/workflow-parser/templates/schema/definition";
import {BasicExpressionToken} from "@actions/workflow-parser/templates/tokens/basic-expression-token";
import {StringToken} from "@actions/workflow-parser/templates/tokens/string-token";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {TokenRange} from "@actions/workflow-parser/templates/tokens/token-range";
import {File} from "@actions/workflow-parser/workflows/file";
import {FileProvider} from "@actions/workflow-parser/workflows/file-provider";
import {Scalar} from "yaml";
import {TextDocument} from "vscode-languageserver-textdocument";
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
import {ActionMetadata, ActionReference} from "./action";
@@ -106,6 +108,8 @@ async function additionalValidations(
config?.contextProviderConfig,
getProviderContext(documentUri, template, root, token.range)
);
validateChomp(diagnostics, token, parent, key, validationDefinition);
}
if (token.definition?.key === "regular-step" && token.range) {
@@ -217,3 +221,98 @@ async function validateExpression(
);
}
}
function validateChomp(
diagnostics: Diagnostic[],
token: BasicExpressionToken,
parent: TemplateToken | undefined,
key: TemplateToken | undefined,
validationDefinition: Definition | undefined
): void {
// Not "clip" or "keep" chomp style?
if (token.chompStyle !== "clip" && token.chompStyle !== "keep") {
return;
}
// No definition? This can happen when the token is in an invalid position or the workflow has parse errors.
if (!validationDefinition) {
return;
}
// Step "run"?
if (parent?.definition?.key === "run-step" && key?.isScalar && key.toString() === "run") {
return;
}
// Block scalar indicator, i.e. | or >
const scalarIndicator = token.scalarType === Scalar.BLOCK_LITERAL ? "|" : ">";
const defKey = validationDefinition.key;
const parentDefKey = parent?.definition?.key;
// Error for boolean fields
if (
defKey === "job-if" ||
defKey === "step-if" ||
defKey === "step-continue-on-error" ||
(parentDefKey === "job-factory" && key?.isScalar && key.toString() === "continue-on-error")
) {
diagnostics.push({
message: `Block scalar adds trailing newline which breaks boolean evaluation. Use '${scalarIndicator}-' to strip trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Error,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
// Error for number fields
else if (
defKey === "step-timeout-minutes" ||
(parentDefKey === "container-mapping" && key?.isScalar && ["ports", "volumes"].includes(key.toString())) ||
(parentDefKey === "job-factory" && key?.isScalar && key.toString() === "timeout-minutes")
) {
diagnostics.push({
message: `Block scalar adds trailing newline which breaks number parsing. Use '${scalarIndicator}-' to strip trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Error,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
// Error for specific string fields
else if (
defKey === "run-name" ||
defKey === "step-name" ||
defKey === "container" ||
defKey === "services-container" ||
defKey === "job-environment" ||
defKey === "job-environment-name" ||
defKey === "runs-on" ||
defKey === "runs-on-labels" ||
(parentDefKey === "container-mapping" && key?.isScalar && ["image", "credentials"].includes(key.toString())) ||
(parentDefKey === "job-defaults-run" && key?.isScalar && ["shell", "working-directory"].includes(key.toString())) ||
(parentDefKey === "job-environment-mapping" && key?.isScalar && key.toString() === "url") ||
(parentDefKey === "job-factory" && key?.isScalar && key.toString() === "name") ||
(parentDefKey === "workflow-job" && key?.isScalar && key.toString() === "name") ||
(parentDefKey === "run-step" && key?.isScalar && key.toString() === "working-directory") ||
(parentDefKey === "runs-on-mapping" && key?.isScalar && key.toString() === "group")
) {
diagnostics.push({
message: `Block scalar adds trailing newline. Use '${scalarIndicator}-' to strip trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Error,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
// Warning for everything else, but only on clip (default)
else if (token.chompStyle === "clip") {
diagnostics.push({
message: `Block scalar adds trailing newline to expression result. Use '${scalarIndicator}-' to strip or '${scalarIndicator}+' to keep trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Warning,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
}
+1 -1
View File
@@ -6,5 +6,5 @@
"languageservice",
"languageserver"
],
"version": "0.3.19"
"version": "0.3.20"
}
+9 -9
View File
@@ -135,7 +135,7 @@
},
"expressions": {
"name": "@actions/expressions",
"version": "0.3.19",
"version": "0.3.20",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.0.3",
@@ -395,11 +395,11 @@
},
"languageserver": {
"name": "@actions/languageserver",
"version": "0.3.19",
"version": "0.3.20",
"license": "MIT",
"dependencies": {
"@actions/languageservice": "^0.3.19",
"@actions/workflow-parser": "^0.3.19",
"@actions/languageservice": "^0.3.20",
"@actions/workflow-parser": "^0.3.20",
"@octokit/rest": "^21.1.1",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
@@ -921,11 +921,11 @@
},
"languageservice": {
"name": "@actions/languageservice",
"version": "0.3.19",
"version": "0.3.20",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.19",
"@actions/workflow-parser": "^0.3.19",
"@actions/expressions": "^0.3.20",
"@actions/workflow-parser": "^0.3.20",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
@@ -12917,10 +12917,10 @@
},
"workflow-parser": {
"name": "@actions/workflow-parser",
"version": "0.3.19",
"version": "0.3.20",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.19",
"@actions/expressions": "^0.3.20",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/workflow-parser",
"version": "0.3.19",
"version": "0.3.20",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -45,7 +45,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.19",
"@actions/expressions": "^0.3.20",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+280
View File
@@ -200,4 +200,284 @@ jobs:
throw new Error("expected if to be a basic expression");
}
});
describe("Block scalar chomp style preservation", () => {
it("preserves clip chomping (|) for literal block scalar", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("clip");
});
it("preserves strip chomping (|-) for literal block scalar", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |-
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("strip");
});
it("preserves keep chomping (|+) for literal block scalar", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |+
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("keep");
});
it("preserves folded clip (>) chomping", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: >
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_FOLDED");
expect(ifToken.chompStyle).toBe("clip");
});
it("preserves folded strip (>-) chomping", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: >-
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_FOLDED");
expect(ifToken.chompStyle).toBe("strip");
});
it("preserves with explicit indent (|2)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |2
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("clip");
});
it("preserves with explicit indent and strip (|-2)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |-2
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("strip");
});
it("handles flow scalars (no chomp info for inline)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: \${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBeUndefined();
expect(ifToken.chompStyle).toBeUndefined();
});
it("handles job-if without ${{ }} (isExpression case)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |
github.event_name == 'push'
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("clip");
});
});
});
@@ -1,6 +1,7 @@
// template-reader *just* does schema validation
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {Scalar} from "yaml";
import {ObjectReader} from "./object-reader";
import {TemplateSchema} from "./schema";
import {DefinitionInfo} from "./schema/definition-info";
@@ -459,7 +460,20 @@ class TemplateReader {
// Doesn't contain "${{"
// Check if value should still be evaluated as an expression
if (definitionInfo.definition instanceof StringDefinition && definitionInfo.definition.isExpression) {
const expression = this.parseIntoExpressionToken(token.range!, raw, allowedContext, token, definitionInfo);
// For isExpression fields (e.g., 'if' conditions), parse token.value (not raw).
// Example YAML:
// if: |
// github.event_name == 'push'
// token.source/raw = "|\n github.event_name == 'push'\n" (includes block scalar header)
// token.value = "github.event_name == 'push'\n" (clean expression content)
// We need token.value because the '|' would interfere with expression parsing.
const expression = this.parseIntoExpressionToken(
token.range!,
token.value,
allowedContext,
token,
definitionInfo
);
if (expression) {
return expression;
}
@@ -606,13 +620,17 @@ class TemplateReader {
}
}
const blockScalarInfo = parseBlockScalarInfo(token);
return new BasicExpressionToken(
this._fileId,
token.range,
`format('${format.join("")}'${args.join("")})`,
definitionInfo,
expressionTokens,
raw
raw,
undefined,
blockScalarInfo.scalarType,
blockScalarInfo.chompStyle
);
}
@@ -686,6 +704,7 @@ class TemplateReader {
};
// Return the expression
const blockScalarInfo = parseBlockScalarInfo(token);
return <ParseExpressionResult>{
expression: new BasicExpressionToken(
this._fileId,
@@ -694,7 +713,9 @@ class TemplateReader {
definitionInfo,
undefined,
token.source,
expressionRange
expressionRange,
blockScalarInfo.scalarType,
blockScalarInfo.chompStyle
),
error: undefined
};
@@ -801,3 +822,53 @@ interface MatchDirectiveResult {
parameters: string[];
error: Error | undefined;
}
interface BlockScalarInfo {
scalarType: Scalar.Type | undefined;
chompStyle: "clip" | "strip" | "keep" | undefined;
}
/**
* Parse the block scalar info from the StringToken
* @param token The StringToken that may contain block scalar information
* @returns The scalar type and chomp style
*/
function parseBlockScalarInfo(token: StringToken): BlockScalarInfo {
const scalarType = token.scalarType;
// Only block scalars have chomp styles
if (scalarType !== Scalar.BLOCK_LITERAL && scalarType !== Scalar.BLOCK_FOLDED) {
return {scalarType: undefined, chompStyle: undefined};
}
// Parse chomp style from the block scalar header
// Look for block scalar indicators at the start: | or >
// Followed by optional chomp indicator (-, +) and/or explicit indent (digit)
// Examples: |, |-, |+, |2, |-2, |+2, >, >-, >+, >2, >-2, >+2
const header = token.blockScalarHeader;
if (!header) {
// If there's no header, assume clip (default)
return {scalarType, chompStyle: "clip"};
}
const blockScalarMatch = header.match(/^(\||>)([-+])?(\d)?/);
if (!blockScalarMatch) {
// Assume clip if we can't parse the indicator
return {scalarType, chompStyle: "clip"};
}
const chompIndicator = blockScalarMatch[2];
let chompStyle: "clip" | "strip" | "keep";
if (chompIndicator === "-") {
chompStyle = "strip";
} else if (chompIndicator === "+") {
chompStyle = "keep";
} else {
// No chomp indicator means clip (default)
chompStyle = "clip";
}
return {scalarType, chompStyle};
}
@@ -1,3 +1,4 @@
import {Scalar} from "yaml";
import {DefinitionInfo} from "../schema/definition-info";
import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "../template-constants";
@@ -23,6 +24,16 @@ export class BasicExpressionToken extends ExpressionToken {
*/
public readonly expressionRange: TokenRange | undefined;
/**
* The YAML scalar type (e.g., BLOCK_LITERAL, BLOCK_FOLDED, PLAIN, etc.) if the expression was parsed from a block scalar.
*/
public readonly scalarType: Scalar.Type | undefined;
/**
* The chomp style of the block scalar: 'clip' (default, keeps one newline), 'strip' (removes all), or 'keep' (keeps all).
*/
public readonly chompStyle: "clip" | "strip" | "keep" | undefined;
/**
* @param originalExpressions If the basic expression was transformed from individual expressions, these will be the original ones
*/
@@ -33,13 +44,17 @@ export class BasicExpressionToken extends ExpressionToken {
definitionInfo: DefinitionInfo | undefined,
originalExpressions: BasicExpressionToken[] | undefined,
source: string | undefined,
expressionRange?: TokenRange | undefined
expressionRange?: TokenRange | undefined,
scalarType?: Scalar.Type | undefined,
chompStyle?: "clip" | "strip" | "keep" | undefined
) {
super(TokenType.BasicExpression, file, range, undefined, definitionInfo);
this.expr = expression;
this.source = source;
this.originalExpressions = originalExpressions;
this.expressionRange = expressionRange;
this.scalarType = scalarType;
this.chompStyle = chompStyle;
}
public get expression(): string {
@@ -55,7 +70,9 @@ export class BasicExpressionToken extends ExpressionToken {
this.definitionInfo,
this.originalExpressions,
this.source,
this.expressionRange
this.expressionRange,
this.scalarType,
this.chompStyle
)
: new BasicExpressionToken(
this.file,
@@ -64,7 +81,9 @@ export class BasicExpressionToken extends ExpressionToken {
this.definitionInfo,
this.originalExpressions,
this.source,
this.expressionRange
this.expressionRange,
this.scalarType,
this.chompStyle
);
}
@@ -1,3 +1,4 @@
import {Scalar} from "yaml";
import {LiteralToken, TemplateToken} from ".";
import {DefinitionInfo} from "../schema/definition-info";
import {TokenRange} from "./token-range";
@@ -6,23 +7,45 @@ import {TokenType} from "./types";
export class StringToken extends LiteralToken {
public readonly value: string;
public readonly source: string | undefined;
public readonly scalarType: Scalar.Type | undefined;
public readonly blockScalarHeader: string | undefined;
public constructor(
file: number | undefined,
range: TokenRange | undefined,
value: string,
definitionInfo: DefinitionInfo | undefined,
source?: string
source?: string,
scalarType?: Scalar.Type,
blockScalarHeader?: string
) {
super(TokenType.String, file, range, definitionInfo);
this.value = value;
this.source = source;
this.scalarType = scalarType;
this.blockScalarHeader = blockScalarHeader;
}
public override clone(omitSource?: boolean): TemplateToken {
return omitSource
? new StringToken(undefined, undefined, this.value, this.definitionInfo, this.source)
: new StringToken(this.file, this.range, this.value, this.definitionInfo, this.source);
? new StringToken(
undefined,
undefined,
this.value,
this.definitionInfo,
this.source,
this.scalarType,
this.blockScalarHeader
)
: new StringToken(
this.file,
this.range,
this.value,
this.definitionInfo,
this.source,
this.scalarType,
this.blockScalarHeader
);
}
public override toString(): string {
+4
View File
@@ -1578,6 +1578,10 @@
"type": "permission-level-any",
"description": "Actions workflows, workflow runs, and artifacts."
},
"artifact-metadata": {
"type": "permission-level-any",
"description": "Storage and deployment records for build artifacts."
},
"attestations": {
"type": "permission-level-any",
"description": "Artifact attestations."
@@ -117,11 +117,28 @@ export class YamlObjectReader implements ObjectReader {
return new BooleanToken(fileId, range, value, undefined);
case "string": {
let source: string | undefined;
const scalarType = token.type;
let blockScalarHeader: string | undefined;
if (token.srcToken && "source" in token.srcToken) {
source = token.srcToken.source;
// Extract block scalar header. For example |-, |+, >-
//
// This relies on undocumented internal behavior (srcToken.props).
// Feature request for official support: https://github.com/eemeli/yaml/issues/643
if (token.srcToken.type === "block-scalar" && "props" in token.srcToken) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = token.srcToken.props as any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const headerProp = props.find((p: any) => p.type === "block-scalar-header");
if (headerProp && "source" in headerProp) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
blockScalarHeader = headerProp.source;
}
}
}
return new StringToken(fileId, range, value, undefined, source);
return new StringToken(fileId, range, value, undefined, source, scalarType, blockScalarHeader);
}
default:
throw new Error(`Unexpected value type '${typeof value}' when reading object`);