Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions language/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Language Grammars And Syntax Tests

Workflow grammar assets live in `language/syntaxes/`.

For syntax-highlighting triage guidance and fixture-based regression test patterns, see:

- `src/workflow/syntax/README.md`
127 changes: 94 additions & 33 deletions language/syntaxes/expressions.tmGrammar.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,76 @@
"injectionSelector": "L:source.github-actions-workflow",
"patterns": [
{
"include": "#expression"
"include": "#block-inline-expression"
},
{
"include": "#block-if-expression"
},
{
"include": "#if-expression"
}
],
"repository": {
"expression": {
"match": "[|-]?\\$\\{\\{(.*?)\\}\\}",
"block-inline-expression": {
"name": "meta.embedded.block.github-actions-expression",
"captures": {
"begin": "[|-]?\\$\\{\\{",
"end": "\\}\\}",
"patterns": [
{
"include": "#expression"
}
]
},
"block-if-expression": {
"contentName": "meta.embedded.block.github-actions-expression",
"begin": "^\\s*\\b(if:) (?:(\\|)|(>))([1-9])?([-+])?(.*\\n?)",
"beginCaptures": {
"1": {
"patterns": [
{
"include": "#function-call"
},
{
"include": "#context"
},
{
"include": "#string"
},
"include": "source.github-actions-workflow"
}
]
},
"2": {
"name": "keyword.control.flow.block-scalar.literal.yaml"
},
"3": {
"name": "keyword.control.flow.block-scalar.folded.yaml"
},
"4": {
"name": "constant.numeric.indentation-indicator.yaml"
},
"5": {
"name": "storage.modifier.chomping-indicator.yaml"
},
"6": {
"patterns": [
{
"include": "#number"
"include": "#comment"
},
{
"include": "#boolean"
},
"match": ".+",
"name": "invalid.illegal.expected-comment-or-newline.yaml"
}
]
}
},
"end": "^(?=\\S)|(?!\\G)",
"patterns": [
{
"begin": "^([ ]+)(?! )",
"end": "^(?!\\1|\\s*$)",
"patterns": [
{
"include": "#null"
"include": "#expression"
}
]
}
}
]
},
"if-expression": {
"match": "\\b(if:) (.*?)$",
"match": "\\b(if:)\\s+((?:'(?:''|[^'])*'|[^#\\n])+?)(\\s+#.*)?$",
"contentName": "meta.embedded.block.github-actions-expression",
"captures": {
"1": {
Expand All @@ -52,27 +85,47 @@
"2": {
"patterns": [
{
"include": "#function-call"
},
{
"include": "#context"
},
{
"include": "#string"
},
{
"include": "#number"
},
{
"include": "#boolean"
},
"include": "#expression"
}
]
},
"3": {
"patterns": [
{
"include": "#null"
"include": "source.github-actions-workflow"
}
]
}
}
},
"expression": {
"patterns": [
{
"include": "#function-call"
},
{
"include": "#context"
},
{
"include": "#string"
},
{
"include": "#op-comparison"
},
{
"include": "#op-logical"
},
{
"include": "#number"
},
{
"include": "#boolean"
},
{
"include": "#null"
}
]
},
"function-call": {
"patterns": [
{
Expand All @@ -98,6 +151,14 @@
"begin": "'",
"end": "'"
},
"op-comparison": {
"name": "keyword.operator.comparison.github-actions-expression",
"match": "(==|!=)"
},
"op-logical": {
"name": "keyword.operator.logical.github-actions-expression",
"match": "(&&|\\|\\|)"
},
"number": {
"name": "constant.numeric.github-actions-expression",
"match": "\\b[0-9]+(?:.[0-9]+)?\\b"
Expand Down
1 change: 1 addition & 0 deletions src/secrets/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {describe, expect, it} from "@jest/globals";
import libsodium from "libsodium-wrappers";
import {encodeSecret} from "./index";

Expand Down
124 changes: 124 additions & 0 deletions src/workflow/syntax/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Workflow Syntax Highlighting Triage & Tests

This note documents a lightweight process for triaging syntax-highlighting bugs in workflow files and turning them into fixture-based regression tests.

## What this covers

This is for **TextMate grammar / tokenization** issues in:

- `language/syntaxes/yaml.tmLanguage.json`
- `language/syntaxes/expressions.tmGrammar.json`
- workflow syntax injection grammars (for embedded languages)

This is **not** the right path for:

- parser/validation diagnostics from language services
- schema/completion issues
- runtime extension behavior

## Triage checklist (quick)

1. Reproduce in `GitHub Actions Workflow` language mode.
2. Run `Developer: Inspect Editor Tokens and Scopes`.
3. Check whether the bug is:
- wrong token scopes/colors (grammar bug)
- a diagnostic/problem message (language service/parser bug)
4. Identify likely grammar file:
- inline `${{ }}` / `if:` expression behavior: `language/syntaxes/expressions.tmGrammar.json`
- general YAML tokenization/comments/keys/scalars: `language/syntaxes/yaml.tmLanguage.json`
- embedded JS/shell/etc: injection grammar(s)
5. Add a fixture and a focused regression test before patching.

## What to ask for in a bug report

If the issue is syntax highlighting, ask for:

- a minimal workflow snippet (`.yml`)
- exact line/token that looks wrong
- screenshot (optional but helpful)
- token inspector output for the wrong token (`textmate scopes`)
- expected behavior (what scope/color should have happened)

Ideally, contributors can include a minimal repro snippet that can be copied directly into a fixture file.

## Current test utilities

Shared helpers live in:

- `src/workflow/syntax/syntax-test-utils.ts`

Current tests live in:

- `src/workflow/syntax/*.test.ts`

Current fixture files live in:

- `src/workflow/syntax/fixtures/`

The helpers are intentionally lightweight and focus on grammar-regression behavior (not VS Code integration tests).

## Which helper to use

- `readJson(relativePath)`
- Use to load grammar JSON files from `language/syntaxes/`.
- `readFixture(relativePath)`
- Use to load YAML fixture files from `src/workflow/syntax/fixtures/`.
- `analyzeSingleOuterEmbeddedBlockFixture(...)`
- Use when grammar has one outer context and one embedded block rule inside it (for example `github-script` + `with.script`).
- `analyzeTopLevelInjectionContexts(...)`
- Use when grammar has multiple top-level included contexts (for example `run` + `shell` per-shell contexts).
- `findGithubActionsInlineExpression(line)`
- Use in expression-regression tests that need to ensure `${{ ... }}` does not terminate on `}}` inside quoted strings (for example `#223`).

## Fixture naming

Use behavior-based, kebab-case fixture names:

- format: `<behavior>.yml`
- examples:
- `if-comment-after-string.yml`
- `expression-nested-braces.yml`
- `run-shell-embedded.yml`

Avoid issue-number-only names in fixture filenames. Issue references should live in test comments or fixture comments.

## Adding a new grammar regression test

1. Add a minimal fixture file under `src/workflow/syntax/fixtures/`
2. Add/extend a Jest test in `src/workflow/syntax/*.test.ts`
3. Keep assertions narrow (what should be embedded, what should not be consumed, header/body boundaries, etc.)
4. Run `npm test`

## Example: `#531`-style triage (inline comment after `if:`)

Issue type:

- likely grammar tokenization bug in `language/syntaxes/expressions.tmGrammar.json`
- symptom: `if: ... 'string' # comment` does not highlight the comment as a YAML comment

Suggested test plan:

1. Add a fixture with lines like:

```yaml
jobs:
test:
if: matrix.os != 'macos-latest' # Cache causes errors on macOS
```

1. Add a focused test for the `if-expression` rule behavior in `expressions.tmGrammar.json`
2. Verify the expression matcher does not swallow the trailing comment, while preserving `#` inside quoted strings

Note:

- This kind of issue may need a new small helper in `syntax-test-utils.ts` for line/capture-level grammar matching, in addition to the embedded-block helpers already present.

## Proposed pattern for community-submitted failing tests

For syntax-highlighting bugs in this area, contributors can submit:

1. A fixture file in `src/workflow/syntax/fixtures/`
2. A failing Jest assertion in `src/workflow/syntax/*.test.ts`
3. A short comment linking the issue number and describing the expected scopes/behavior

That gives maintainers a reproducible regression case even before a fix is implemented.
Loading
Loading