Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
```

2. Add a focused test for the `if-expression` rule behavior in `expressions.tmGrammar.json`
3. 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