From a00b8851d280b3ad9ef401c620004f4311edc1b3 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Thu, 25 Jun 2026 11:56:30 +0200 Subject: [PATCH 1/2] fix: detect grouped workflow outputs --- CHANGELOG.md | 4 +++ .../syntax/WorkflowContextCatalog.java | 4 +-- .../githubworkflow/syntax/WorkflowPsi.java | 10 +++++++ .../syntax/WorkflowSyntaxCompletionTest.java | 19 ++++++++++++++ .../syntax/WorkflowValidationTest.java | 26 +++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e35bb..915fdc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +### Fixes + +- Step outputs written inside grouped shell redirects to `$GITHUB_OUTPUT` are now recognized. + ## [2026.6.20] - 2026-06-20 ### Plugin Wiring diff --git a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java index 3659a8b..89cc4ae 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java @@ -2,8 +2,6 @@ import com.github.yunabraska.githubworkflow.i18n.GitHubWorkflowBundle; - - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -21,6 +19,8 @@ public class WorkflowContextCatalog { public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?"); public static final Pattern PATTERN_GITHUB_OUTPUT_TEE = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*\\|\\s*tee\\s+(?:-[A-Za-z]+\\s+)*.*\\$\\w*:?\\{?GITHUB_OUTPUT\\}?"); + public static final Pattern PATTERN_GITHUB_OUTPUT_GROUP = Pattern.compile("(?m)^\\s*\\{\\s*\\R([\\s\\S]*?)^\\s*}\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?"); + public static final Pattern PATTERN_SHELL_OUTPUT_ASSIGNMENT = Pattern.compile("(?m)^\\s*(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*;?\\s*$"); public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_ENV\\}?\"?"); public static final Pattern PATTERN_GITHUB_OUTPUT_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?"); public static final Pattern PATTERN_GITHUB_ENV_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?"); diff --git a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java index d12285a..461faba 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java @@ -43,8 +43,10 @@ import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_ENV; import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_ENV_MULTILINE; import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT; +import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT_GROUP; import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT_MULTILINE; import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT_TEE; +import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_SHELL_OUTPUT_ASSIGNMENT; import static java.util.Collections.unmodifiableList; import static java.util.Optional.ofNullable; @@ -335,6 +337,7 @@ private static Map toGithubOutputs(final String text) { if (text.contains("GITHUB_OUTPUT")) { putMatches(variables, PATTERN_GITHUB_OUTPUT.matcher(text), false); putMatches(variables, PATTERN_GITHUB_OUTPUT_TEE.matcher(text), false); + putGroupedOutputMatches(variables, text); putMatches(variables, PATTERN_GITHUB_OUTPUT_MULTILINE.matcher(text), true); } return variables; @@ -349,6 +352,13 @@ private static Map toGithubEnvs(final String text) { return variables; } + private static void putGroupedOutputMatches(final Map variables, final String text) { + final Matcher matcher = PATTERN_GITHUB_OUTPUT_GROUP.matcher(text); + while (matcher.find()) { + putMatches(variables, PATTERN_SHELL_OUTPUT_ASSIGNMENT.matcher(matcher.group(1)), false); + } + } + private static void putMatches(final Map variables, final Matcher matcher, final boolean multiline) { while (matcher.find()) { if (matcher.groupCount() >= 1) { diff --git a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java index 8f7023b..1a22b6c 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java @@ -1493,6 +1493,25 @@ public void testStepsOutputCompletionSuggestsRunOutput() { """)).contains("artifact"); } + public void testStepsOutputCompletionSuggestsGroupedRunOutput() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: prepare + run: | + echo "output1=value1" >> "${GITHUB_OUTPUT}" + { + echo "output2=value2" + echo "output3=value3" + } >> "${GITHUB_OUTPUT}" + - run: echo "${{ steps.prepare.outputs. }}" + """)).contains("output1", "output2", "output3"); + } + public void testBracketStepsOutputCompletionSuggestsRunOutput() { assertThat(completeWorkflow(""" name: Completion diff --git a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java index 0ee29ad..066e44d 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java @@ -1261,6 +1261,32 @@ public void testIssue79MultilineStepOutputFromGroupedEchoIsAccepted() { """); } + public void testIssue94StepOutputFromGroupedEchoIsAccepted() { + assertWorkflowHighlights(""" + name: grouping bug + on: + workflow_dispatch: + jobs: + grouping-bug: + runs-on: ubuntu-latest + steps: + - name: Prepare outputs + id: prepare + run: | + echo "output1=value1" >> "${GITHUB_OUTPUT}" + + { + echo "output2=value2" + echo "output3=value3" + } >> "${GITHUB_OUTPUT}" + - name: Print outputs + run: | + echo "Output 1: ${{ steps.prepare.outputs.output1 }}" + echo "Output 2: ${{ steps.prepare.outputs.output2 }}" + echo "Output 3: ${{ steps.prepare.outputs.output3 }}" + """); + } + public void testIssue73StepOutputFromTeePipeIsAccepted() { assertWorkflowHighlights(""" name: Issue 73 From ace031307370dfcfbad7a8019b7ee798e7821fb8 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Thu, 25 Jun 2026 14:24:52 +0200 Subject: [PATCH 2/2] fix: cover gitea grouped outputs --- CHANGELOG.md | 2 +- .../syntax/WorkflowContextCatalog.java | 8 +++--- .../githubworkflow/syntax/WorkflowPsi.java | 4 +-- .../run/WorkflowGutterTest.java | 22 ++++++++++++++++ .../syntax/WorkflowSyntaxCompletionTest.java | 19 ++++++++++++++ .../syntax/WorkflowValidationTest.java | 26 +++++++++++++++++++ 6 files changed, 74 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 915fdc1..4e18670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Fixes -- Step outputs written inside grouped shell redirects to `$GITHUB_OUTPUT` are now recognized. +- Step outputs written inside grouped shell redirects to `$GITHUB_OUTPUT` or `$GITEA_OUTPUT` are now recognized. ## [2026.6.20] - 2026-06-20 diff --git a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java index 89cc4ae..ea131ef 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowContextCatalog.java @@ -17,11 +17,11 @@ @SuppressWarnings("java:S2386") public class WorkflowContextCatalog { - public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?"); - public static final Pattern PATTERN_GITHUB_OUTPUT_TEE = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*\\|\\s*tee\\s+(?:-[A-Za-z]+\\s+)*.*\\$\\w*:?\\{?GITHUB_OUTPUT\\}?"); - public static final Pattern PATTERN_GITHUB_OUTPUT_GROUP = Pattern.compile("(?m)^\\s*\\{\\s*\\R([\\s\\S]*?)^\\s*}\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?"); + public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?(?:GITHUB|GITEA)_OUTPUT\\}?\"?"); + public static final Pattern PATTERN_GITHUB_OUTPUT_TEE = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*\\|\\s*tee\\s+(?:-[A-Za-z]+\\s+)*.*\\$\\w*:?\\{?(?:GITHUB|GITEA)_OUTPUT\\}?"); + public static final Pattern PATTERN_GITHUB_OUTPUT_GROUP = Pattern.compile("(?m)^\\s*\\{\\s*\\R([\\s\\S]*?)^\\s*}\\s*>>\\s*\"?\\$\\w*:?\\{?(?:GITHUB|GITEA)_OUTPUT\\}?\"?"); public static final Pattern PATTERN_SHELL_OUTPUT_ASSIGNMENT = Pattern.compile("(?m)^\\s*(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*;?\\s*$"); - public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_ENV\\}?\"?"); + public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?(?:GITHUB|GITEA)_ENV\\}?\"?"); public static final Pattern PATTERN_GITHUB_OUTPUT_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?"); public static final Pattern PATTERN_GITHUB_ENV_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?"); public static final long CACHE_ONE_DAY = 24L * 60 * 60 * 1000; diff --git a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java index 461faba..4d598f1 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/syntax/WorkflowPsi.java @@ -334,7 +334,7 @@ public static String goToDeclarationString() { private static Map toGithubOutputs(final String text) { final Map variables = new HashMap<>(); - if (text.contains("GITHUB_OUTPUT")) { + if (text.contains("GITHUB_OUTPUT") || text.contains("GITEA_OUTPUT")) { putMatches(variables, PATTERN_GITHUB_OUTPUT.matcher(text), false); putMatches(variables, PATTERN_GITHUB_OUTPUT_TEE.matcher(text), false); putGroupedOutputMatches(variables, text); @@ -345,7 +345,7 @@ private static Map toGithubOutputs(final String text) { private static Map toGithubEnvs(final String text) { final Map variables = new HashMap<>(); - if (text.contains("GITHUB_ENV")) { + if (text.contains("GITHUB_ENV") || text.contains("GITEA_ENV")) { putMatches(variables, PATTERN_GITHUB_ENV.matcher(text), false); putMatches(variables, PATTERN_GITHUB_ENV_MULTILINE.matcher(text), true); } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/run/WorkflowGutterTest.java b/src/test/java/com/github/yunabraska/githubworkflow/run/WorkflowGutterTest.java index ed5aca0..bee9ca3 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/run/WorkflowGutterTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/run/WorkflowGutterTest.java @@ -183,6 +183,28 @@ public void testWorkflowDispatchShowsRunLineMarker() throws Exception { } } + public void testWorkflowDispatchShowsOneRunLineMarker() { + configureWorkflowProjectFile(""" + name: Gutter + on: + workflow_dispatch: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + final WorkflowRun.LineMarkerContributor.RepositoryAvailability previous = + WorkflowRun.LineMarkerContributor.useRepositoryAvailabilityForTests((project, file) -> true); + try { + assertThat(myFixture.findAllGutters().stream() + .filter(gutter -> gutter.getIcon() == AllIcons.Actions.Execute) + .count()).isEqualTo(1); + } finally { + WorkflowRun.LineMarkerContributor.useRepositoryAvailabilityForTests(previous); + } + } + public void testWorkflowDispatchDoesNotShowRunLineMarkerWithoutGitRepository() { configureWorkflowProjectFile(""" name: Gutter diff --git a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java index 1a22b6c..d286d59 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowSyntaxCompletionTest.java @@ -1512,6 +1512,25 @@ public void testStepsOutputCompletionSuggestsGroupedRunOutput() { """)).contains("output1", "output2", "output3"); } + public void testGiteaStepsOutputCompletionSuggestsGroupedRunOutput() { + assertThat(completeGiteaWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: prepare + run: | + echo "output1=value1" >> "${GITEA_OUTPUT}" + { + echo "output2=value2" + echo "output3=value3" + } >> "${GITEA_OUTPUT}" + - run: echo "${{ steps.prepare.outputs. }}" + """)).contains("output1", "output2", "output3"); + } + public void testBracketStepsOutputCompletionSuggestsRunOutput() { assertThat(completeWorkflow(""" name: Completion diff --git a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java index 066e44d..1cea406 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/syntax/WorkflowValidationTest.java @@ -1287,6 +1287,32 @@ public void testIssue94StepOutputFromGroupedEchoIsAccepted() { """); } + public void testGiteaStepOutputFromGroupedEchoIsAccepted() { + assertGiteaWorkflowHighlights(""" + name: grouping bug + on: + workflow_dispatch: + jobs: + grouping-bug: + runs-on: ubuntu-latest + steps: + - name: Prepare outputs + id: prepare + run: | + echo "output1=value1" >> "${GITEA_OUTPUT}" + + { + echo "output2=value2" + echo "output3=value3" + } >> "${GITEA_OUTPUT}" + - name: Print outputs + run: | + echo "Output 1: ${{ steps.prepare.outputs.output1 }}" + echo "Output 2: ${{ steps.prepare.outputs.output2 }}" + echo "Output 3: ${{ steps.prepare.outputs.output3 }}" + """); + } + public void testIssue73StepOutputFromTeePipeIsAccepted() { assertWorkflowHighlights(""" name: Issue 73