docs(action): recount the cases, document runner-mode keys, int32 and folding, note the Lint null key
- commit
- 43e36633986e9bf19fcca255953bd7666031cb09
- author
- brice · · 2026-10-13 14:10 UTC
- committer
- brice · · 2026-10-18 14:59 UTC
- parent
- 110f7f5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +32 -7
M action/PORTING.md +16 -3
| @@ -24,7 +24,7 @@ most files and differ on a few (below); the new one is not reproduced. | |||
| 24 | 24 | ## Checks | |
| 25 | 25 | ||
| 26 | 26 | - `testdata/runner.json`: the answer of the runner's loader on every file of | |
| 27 | - | `testdata/cases` (299 files). `gen/runner/Driver.cs` is | |
| 27 | + | `testdata/cases` (338 files). `gen/runner/Driver.cs` is | |
| 28 | 28 | `ActionManifestManagerLegacy.Load/ConvertRuns/ConvertInputs` with what needs | |
| 29 | 29 | an `IExecutionContext` removed (tracing, the file table, expression values: | |
| 30 | 30 | none of it changes loading), compiled into a copy of `src/Sdk` with | |
| @@ -40,7 +40,7 @@ most files and differ on a few (below); the new one is not reproduced. | |||
| 40 | 40 | (`TestParseUsesAgainstRunner`). | |
| 41 | 41 | - `testdata/strict.json`: `parseAction` + `convertActionTemplate` of the npm | |
| 42 | 42 | package on every case; `Lint` is compared on the error list | |
| 43 | - | (`TestLintAgainstReference`). One file is excused with its reason | |
| 43 | + | (`TestLintAgainstReference`). Eleven files are excused, each with its reason | |
| 44 | 44 | (`knownLintDifferences`). | |
| 45 | 45 | - `FuzzParse` (no panic, an action iff no error, at most ten errors, every step | |
| 46 | 46 | has an id and a condition) and `FuzzParseUses`. | |
| @@ -49,7 +49,7 @@ Regenerate with `gen/regen.sh` (`RUNNER_CLONE=<clone>` for the runner part); the | |||
| 49 | 49 | diff must be empty. The cases are: the 24 files of the runner's | |
| 50 | 50 | `src/Test/TestData` that are action manifests (`runner_*`), the 96 manifests | |
| 51 | 51 | inlined in the language service tests (`ls_*`: `action-parser.test.ts`, | |
| 52 | - | `validate-action.test.ts`), and 179 written for this package. The | |
| 52 | + | `validate-action.test.ts`), and 218 written for this package. The | |
| 53 | 53 | language service's own expectations (`toBeGreaterThan(0)`) are not ported: the | |
| 54 | 54 | cases are replayed through the reference instead, which gives the messages. | |
| 55 | 55 | ||
| @@ -109,6 +109,13 @@ All verified by `TestAgainstRunner`. | |||
| 109 | 109 | parse event`, a collection used as a key `Expected mapping end`. Such an error | |
| 110 | 110 | stops the read where the runner meets it, so errors found earlier in the | |
| 111 | 111 | document stay (`template.Context.RunnerYAML`). | |
| 112 | + | - **Keys, integers, case.** Mapping keys are resolved like values: `~` is null | |
| 113 | + | (`Unexpected value ''`), `0x10` is the key `16`, `1.0` is `1`, `TRUE` is `true`, | |
| 114 | + | so `0x10` and `16` are duplicates. `0x` and `0o` integers, plain or `!!int`, | |
| 115 | + | are int32 with wraparound (`0xFFFFFFFF` is `-1`, `0x80000000` is | |
| 116 | + | `-2147483648`; over 32 bits is an error, and `!!float` refuses them). Duplicate | |
| 117 | + | keys, input names and step ids compare with .NET's OrdinalIgnoreCase | |
| 118 | + | (`expr.OrdinalFold`): `é` and `É` are the same, `s` and `ſ` are not. | |
| 112 | 119 | - **Depth.** `TemplateMemory(maxDepth: 100)`: `Maximum object depth exceeded` | |
| 113 | 120 | from the 100th nested collection, the root mapping being the first. Skipped | |
| 114 | 121 | values (unknown keys) count (`template.Context.MaxDepth`). | |
| @@ -209,6 +216,12 @@ All verified by `TestAgainstRunner`. | |||
| 209 | 216 | - A mapping key that is a collection: the reference reads `undefined` | |
| 210 | 217 | (`Unexpected value 'undefined'`); `Lint` reports `Unexpected value ''` | |
| 211 | 218 | (`knownLintDifferences`). `Parse` is the runner's (`Expected mapping end`). | |
| 219 | + | - `Lint` keeps keys as strings, so it differs from the reference on keys that | |
| 220 | + | are not strings: it misses a null key under a loose-key mapping (`inputs`, | |
| 221 | + | `env`), which the reference and the runner reject, and the reference fails on | |
| 222 | + | other non-string keys with `raw.indexOf is not a function`. Fixing the null | |
| 223 | + | key in `Lint` would change how `template` reads workflow files, so it is | |
| 224 | + | left alone. | |
| 212 | 225 | ||
| 213 | 226 | ## To verify against recordings | |
| 214 | 227 | ||
| @@ -24,7 +24,7 @@ most files and differ on a few (below); the new one is not reproduced. | |||||
| 24 | ## Checks | 24 | ## Checks | ||
| 25 | 25 | ||||
| 26 | - `testdata/runner.json`: the answer of the runner's loader on every file of | 26 | - `testdata/runner.json`: the answer of the runner's loader on every file of | ||
| 27 | - | `testdata/cases` (299 files). `gen/runner/Driver.cs` is | 27 | + | `testdata/cases` (338 files). `gen/runner/Driver.cs` is |
| 28 | `ActionManifestManagerLegacy.Load/ConvertRuns/ConvertInputs` with what needs | 28 | `ActionManifestManagerLegacy.Load/ConvertRuns/ConvertInputs` with what needs | ||
| 29 | an `IExecutionContext` removed (tracing, the file table, expression values: | 29 | an `IExecutionContext` removed (tracing, the file table, expression values: | ||
| 30 | none of it changes loading), compiled into a copy of `src/Sdk` with | 30 | none of it changes loading), compiled into a copy of `src/Sdk` with | ||
| @@ -40,7 +40,7 @@ most files and differ on a few (below); the new one is not reproduced. | |||||
| 40 | (`TestParseUsesAgainstRunner`). | 40 | (`TestParseUsesAgainstRunner`). | ||
| 41 | - `testdata/strict.json`: `parseAction` + `convertActionTemplate` of the npm | 41 | - `testdata/strict.json`: `parseAction` + `convertActionTemplate` of the npm | ||
| 42 | package on every case; `Lint` is compared on the error list | 42 | package on every case; `Lint` is compared on the error list | ||
| 43 | - | (`TestLintAgainstReference`). One file is excused with its reason | 43 | + | (`TestLintAgainstReference`). Eleven files are excused, each with its reason |
| 44 | (`knownLintDifferences`). | 44 | (`knownLintDifferences`). | ||
| 45 | - `FuzzParse` (no panic, an action iff no error, at most ten errors, every step | 45 | - `FuzzParse` (no panic, an action iff no error, at most ten errors, every step | ||
| 46 | has an id and a condition) and `FuzzParseUses`. | 46 | has an id and a condition) and `FuzzParseUses`. | ||
| @@ -49,7 +49,7 @@ Regenerate with `gen/regen.sh` (`RUNNER_CLONE=<clone>` for the runner part); the | |||||
| 49 | diff must be empty. The cases are: the 24 files of the runner's | 49 | diff must be empty. The cases are: the 24 files of the runner's | ||
| 50 | `src/Test/TestData` that are action manifests (`runner_*`), the 96 manifests | 50 | `src/Test/TestData` that are action manifests (`runner_*`), the 96 manifests | ||
| 51 | inlined in the language service tests (`ls_*`: `action-parser.test.ts`, | 51 | inlined in the language service tests (`ls_*`: `action-parser.test.ts`, | ||
| 52 | - | `validate-action.test.ts`), and 179 written for this package. The | 52 | + | `validate-action.test.ts`), and 218 written for this package. The |
| 53 | language service's own expectations (`toBeGreaterThan(0)`) are not ported: the | 53 | language service's own expectations (`toBeGreaterThan(0)`) are not ported: the | ||
| 54 | cases are replayed through the reference instead, which gives the messages. | 54 | cases are replayed through the reference instead, which gives the messages. | ||
| 55 | 55 | ||||
| @@ -109,6 +109,13 @@ All verified by `TestAgainstRunner`. | |||||
| 109 | parse event`, a collection used as a key `Expected mapping end`. Such an error | 109 | parse event`, a collection used as a key `Expected mapping end`. Such an error | ||
| 110 | stops the read where the runner meets it, so errors found earlier in the | 110 | stops the read where the runner meets it, so errors found earlier in the | ||
| 111 | document stay (`template.Context.RunnerYAML`). | 111 | document stay (`template.Context.RunnerYAML`). | ||
| 112 | + | - **Keys, integers, case.** Mapping keys are resolved like values: `~` is null | |||
| 113 | + | (`Unexpected value ''`), `0x10` is the key `16`, `1.0` is `1`, `TRUE` is `true`, | |||
| 114 | + | so `0x10` and `16` are duplicates. `0x` and `0o` integers, plain or `!!int`, | |||
| 115 | + | are int32 with wraparound (`0xFFFFFFFF` is `-1`, `0x80000000` is | |||
| 116 | + | `-2147483648`; over 32 bits is an error, and `!!float` refuses them). Duplicate | |||
| 117 | + | keys, input names and step ids compare with .NET's OrdinalIgnoreCase | |||
| 118 | + | (`expr.OrdinalFold`): `é` and `É` are the same, `s` and `ſ` are not. | |||
| 112 | - **Depth.** `TemplateMemory(maxDepth: 100)`: `Maximum object depth exceeded` | 119 | - **Depth.** `TemplateMemory(maxDepth: 100)`: `Maximum object depth exceeded` | ||
| 113 | from the 100th nested collection, the root mapping being the first. Skipped | 120 | from the 100th nested collection, the root mapping being the first. Skipped | ||
| 114 | values (unknown keys) count (`template.Context.MaxDepth`). | 121 | values (unknown keys) count (`template.Context.MaxDepth`). | ||
| @@ -209,6 +216,12 @@ All verified by `TestAgainstRunner`. | |||||
| 209 | - A mapping key that is a collection: the reference reads `undefined` | 216 | - A mapping key that is a collection: the reference reads `undefined` | ||
| 210 | (`Unexpected value 'undefined'`); `Lint` reports `Unexpected value ''` | 217 | (`Unexpected value 'undefined'`); `Lint` reports `Unexpected value ''` | ||
| 211 | (`knownLintDifferences`). `Parse` is the runner's (`Expected mapping end`). | 218 | (`knownLintDifferences`). `Parse` is the runner's (`Expected mapping end`). | ||
| 219 | + | - `Lint` keeps keys as strings, so it differs from the reference on keys that | |||
| 220 | + | are not strings: it misses a null key under a loose-key mapping (`inputs`, | |||
| 221 | + | `env`), which the reference and the runner reject, and the reference fails on | |||
| 222 | + | other non-string keys with `raw.indexOf is not a function`. Fixing the null | |||
| 223 | + | key in `Lint` would change how `template` reads workflow files, so it is | |||
| 224 | + | left alone. | |||
| 212 | 225 | ||||
| 213 | ## To verify against recordings | 226 | ## To verify against recordings | ||
| 214 | 227 | ||||
M action/lint_test.go +7 -2
| @@ -59,12 +59,17 @@ var knownLintDifferences = map[string]string{ | |||
| 59 | 59 | "key_float_dup.yml": jsKey, | |
| 60 | 60 | "key_hex_dup.yml": jsKey, | |
| 61 | 61 | "key_hex_input.yml": jsKey, | |
| 62 | - | "key_null_env.yml": jsKey, | |
| 63 | - | "key_null_inputs.yml": jsKey, | |
| 62 | + | "key_null_env.yml": nullKey, | |
| 63 | + | "key_null_inputs.yml": nullKey, | |
| 64 | 64 | "key_null_top.yml": jsKey, | |
| 65 | 65 | "key_number_runs.yml": jsKey, | |
| 66 | 66 | } | |
| 67 | 67 | ||
| 68 | + | // nullKey: Lint misses a null key under a loose-key mapping; the reference and | |
| 69 | + | // the runner reject it. Fixing it in template would change how workflow files | |
| 70 | + | // are read, so it is left as it is. | |
| 71 | + | const nullKey = "Lint misses a null key under a loose-key mapping; the reference and the runner reject it" | |
| 72 | + | ||
| 68 | 73 | // jsKey: the reference resolves a plain key to a number, boolean or null (and | |
| 69 | 74 | // then crashes on it, "raw.indexOf is not a function", or words it | |
| 70 | 75 | // differently); template keeps keys of the workflow-style readers as strings. | |
| @@ -59,12 +59,17 @@ var knownLintDifferences = map[string]string{ | |||||
| 59 | "key_float_dup.yml": jsKey, | 59 | "key_float_dup.yml": jsKey, | ||
| 60 | "key_hex_dup.yml": jsKey, | 60 | "key_hex_dup.yml": jsKey, | ||
| 61 | "key_hex_input.yml": jsKey, | 61 | "key_hex_input.yml": jsKey, | ||
| 62 | - | "key_null_env.yml": jsKey, | 62 | + | "key_null_env.yml": nullKey, |
| 63 | - | "key_null_inputs.yml": jsKey, | 63 | + | "key_null_inputs.yml": nullKey, |
| 64 | "key_null_top.yml": jsKey, | 64 | "key_null_top.yml": jsKey, | ||
| 65 | "key_number_runs.yml": jsKey, | 65 | "key_number_runs.yml": jsKey, | ||
| 66 | } | 66 | } | ||
| 67 | 67 | ||||
| 68 | + | // nullKey: Lint misses a null key under a loose-key mapping; the reference and | |||
| 69 | + | // the runner reject it. Fixing it in template would change how workflow files | |||
| 70 | + | // are read, so it is left as it is. | |||
| 71 | + | const nullKey = "Lint misses a null key under a loose-key mapping; the reference and the runner reject it" | |||
| 72 | + | ||||
| 68 | // jsKey: the reference resolves a plain key to a number, boolean or null (and | 73 | // jsKey: the reference resolves a plain key to a number, boolean or null (and | ||
| 69 | // then crashes on it, "raw.indexOf is not a function", or words it | 74 | // then crashes on it, "raw.indexOf is not a function", or words it | ||
| 70 | // differently); template keeps keys of the workflow-style readers as strings. | 75 | // differently); template keeps keys of the workflow-style readers as strings. | ||
M template/PORTING.md +5 -2
| @@ -73,8 +73,11 @@ checked against the runner's own code): `MaxDepth` (`TemplateMemory`'s | |||
| 73 | 73 | `maxDepth`, counting skipped values too), `ErrorLimit` (the runner keeps ten), | |
| 74 | 74 | `RunnerYAML` (anchors and aliases, tags other than `!!bool/!!float/!!int/!!null`, | |
| 75 | 75 | 32-bit limits of `0x`/`0o` integers, a second document, an empty file and a | |
| 76 | - | collection used as a key all end the read with the runner's message; a number | |
| 77 | - | read as a string is shown as .NET's `G15`, `DotNetNumberString`). Without them | |
| 76 | + | collection used as a key all end the read with the runner's message; mapping | |
| 77 | + | keys are resolved like values, `~` giving an empty key and `0x10` the key `16`; | |
| 78 | + | `0x`/`0o` integers wrap as int32, `0xFFFFFFFF` being -1; duplicate keys compare | |
| 79 | + | with .NET's OrdinalIgnoreCase, `expr.OrdinalFold`; a number read as a string is | |
| 80 | + | shown as .NET's `G15`, `DotNetNumberString`). Without them | |
| 78 | 81 | nothing changes. `Error.Exception` marks an error that is a .NET exception the | |
| 79 | 82 | runner reports without a position. | |
| 80 | 83 | ||
| @@ -73,8 +73,11 @@ checked against the runner's own code): `MaxDepth` (`TemplateMemory`'s | |||||
| 73 | `maxDepth`, counting skipped values too), `ErrorLimit` (the runner keeps ten), | 73 | `maxDepth`, counting skipped values too), `ErrorLimit` (the runner keeps ten), | ||
| 74 | `RunnerYAML` (anchors and aliases, tags other than `!!bool/!!float/!!int/!!null`, | 74 | `RunnerYAML` (anchors and aliases, tags other than `!!bool/!!float/!!int/!!null`, | ||
| 75 | 32-bit limits of `0x`/`0o` integers, a second document, an empty file and a | 75 | 32-bit limits of `0x`/`0o` integers, a second document, an empty file and a | ||
| 76 | - | collection used as a key all end the read with the runner's message; a number | 76 | + | collection used as a key all end the read with the runner's message; mapping |
| 77 | - | read as a string is shown as .NET's `G15`, `DotNetNumberString`). Without them | 77 | + | keys are resolved like values, `~` giving an empty key and `0x10` the key `16`; |
| 78 | + | `0x`/`0o` integers wrap as int32, `0xFFFFFFFF` being -1; duplicate keys compare | |||
| 79 | + | with .NET's OrdinalIgnoreCase, `expr.OrdinalFold`; a number read as a string is | |||
| 80 | + | shown as .NET's `G15`, `DotNetNumberString`). Without them | |||
| 78 | nothing changes. `Error.Exception` marks an error that is a .NET exception the | 81 | nothing changes. `Error.Exception` marks an error that is a .NET exception the | ||
| 79 | runner reports without a position. | 82 | runner reports without a position. | ||
| 80 | 83 | ||||
M template/context.go +4 -0
| @@ -95,6 +95,10 @@ type Context struct { | |||
| 95 | 95 | // ("Unexpected tag 'tag:yaml.org,2002:str'": the runner knows no !!str). | |
| 96 | 96 | // Such an error is reported alone, without the schema errors that the | |
| 97 | 97 | // runner would have found in the part of the document before it. | |
| 98 | + | // Mapping keys are resolved like values (~ is a null key, 0x10 is the key | |
| 99 | + | // 16, TRUE is true), 0x and 0o integers wrap as int32 (0xFFFFFFFF is -1), | |
| 100 | + | // and duplicate keys compare with .NET's OrdinalIgnoreCase | |
| 101 | + | // (expr.OrdinalFold) instead of JavaScript's toUpperCase. | |
| 98 | 102 | RunnerYAML bool | |
| 99 | 103 | ||
| 100 | 104 | schema *Schema | |
| @@ -95,6 +95,10 @@ type Context struct { | |||||
| 95 | // ("Unexpected tag 'tag:yaml.org,2002:str'": the runner knows no !!str). | 95 | // ("Unexpected tag 'tag:yaml.org,2002:str'": the runner knows no !!str). | ||
| 96 | // Such an error is reported alone, without the schema errors that the | 96 | // Such an error is reported alone, without the schema errors that the | ||
| 97 | // runner would have found in the part of the document before it. | 97 | // runner would have found in the part of the document before it. | ||
| 98 | + | // Mapping keys are resolved like values (~ is a null key, 0x10 is the key | |||
| 99 | + | // 16, TRUE is true), 0x and 0o integers wrap as int32 (0xFFFFFFFF is -1), | |||
| 100 | + | // and duplicate keys compare with .NET's OrdinalIgnoreCase | |||
| 101 | + | // (expr.OrdinalFold) instead of JavaScript's toUpperCase. | |||
| 98 | RunnerYAML bool | 102 | RunnerYAML bool | ||
| 99 | 103 | ||||
| 100 | schema *Schema | 104 | schema *Schema | ||