Repository navigation
fix(mdcode): report the YAML error for a version an error near it can change - #532
feixufeixu wants to merge 2 commits into
Conversation
… change Past a YAML error other than a key set twice, the loader reads a model's version only when the first line sets it alone and the text holds the word `version` nowhere else. A YAML error on the version's line, or on the lines between it and the next root key, passed that check. So `version: "0.2.0"` followed by ` .dev0"` read as version 0.2.0, and `version: "0.2.0.dev0"#/google"` read as the vanilla version. A second version key written with an escape, such as `"ver\x73ion"`, also passed when a YAML error hid it. A vanilla model read this way gave no block profiles, so `kcmd push --profile prod` said the model defines no profiles. A Google-flavor model read as vanilla had its profile files rejected as sitting beside a vanilla model. The version is now read only when no YAML error falls from its line to the start of the next root key, and the text holds no backslash. Each command then reports the YAML error for these files.
| // Where the next root key starts. | ||
| const pairs = (parsed.contents as yaml.YAMLMap).items; | ||
| const next = pairs[pairs.findIndex(p => p.value === version) + 1]; | ||
| const nextKey = yaml.isNode(next?.key) ? next.key.range?.[0] : undefined; |
There was a problem hiding this comment.
AI identified the following bug as high urgency:
Checking that no YAML error falls at or before the start of the next root key misses two cases where the line right after version: can still change what the version is:
- A split version on the next line followed by
:: When the closing quote onversion:comes too early and the next line has a colon and an error on its value—such asversion: "0.2.0"followed by.dev0": "(which fixes toversion: "0.2.0.dev0"andlabel: "")—the YAML parser treats.dev0"as the next root key and reports the unclosed-quote error on its value, after that key starts. - A quote left open on the next key: When the root key after
version:leaves a quote open that closes on the line after it—such asversion: "0.2.0"followed bylabel: ",.dev0", andx: "(orversion: "0.2.0.dev0/google"followed bylabel: ",0.2.0.dev0", andx: ")—labelswallows the next line as a multiline string and the unclosed-quote error is reported later onx: ".
In both cases, googleBlockProfiles reads the unedited version and returns [] instead of throwing the YAML parse error.
Below is a test case demonstrating the issue:
test('reports the YAML error when the line after the version holds the rest of the version or an open quote', () => {
// Setup: broken documents where the line after `version:` either holds
// the rest of a split version followed by `:` and an unclosed quote, or
// opens a quote on `label:` that takes in the rest of a split version or
// a second version and leaves `x:` with an unclosed quote.
for (const text of [
`
version: "0.2.0"
.dev0": "
semantic_model:
- name: m
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
`
version: "0.2.0"
label: "
.dev0"
x: "
semantic_model:
- name: m
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
`
version: "0.2.0.dev0/google"
label: "
0.2.0.dev0"
x: "
semantic_model:
- name: m
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
]) {
expect(() => loadModels(text)).toThrow('could not parse input');
// Current behavior on this PR: `googleBlockProfiles(text)` returns `[]`
// because `versionPastYamlError` only checks `err.pos[0] <=
// next.key.range?.[0]`, which misses errors on `next.value` or a later
// root key.
// Expected behavior: throws `Semantic model load error: could not parse
// input: ...` because fixing the YAML error yields a vanilla model with
// profile `prod`.
expect(() => googleBlockProfiles(text)).toThrow(
'Semantic model load error: could not parse input: ');
}
});There was a problem hiding this comment.
I removed the first-line rule instead of extending it. The loader now reads the version past a YAML error only when every error is a key set twice, so these files report the YAML error, and your three cases are in the version test.
| @@ -1630,8 +1636,13 @@ function versionPastYamlError( | |||
| if (errors.every(err => err.code === 'DUPLICATE_KEY')) return version.value; | |||
| const first = FIRST_LINE_VERSION.exec(text); | |||
There was a problem hiding this comment.
AI found the following bug (classified as medium urgency):
FIRST_LINE_VERSION still accepts a broken version: line in two cases where fixing the YAML error changes the version:
- A quote left open on
version:and closed on the same line by#"or a mismatched quote: Unlike the unquoted branch, the quoted branches inFIRST_LINE_VERSIONallow#and the opposite quote character. If the version's closing quote is missing and there is no space before#—such asversion: "0.2.0.dev0#"(which fixes toversion: "0.2.0.dev0" # ")—or a mismatched quote such asversion: "0.2.0.dev0'", line 1 parses as"0.2.0.dev0#"/"0.2.0.dev0'"andgoogleBlockProfilestreats the document as non-vanilla instead of reporting a later unclosed quote. - A lone carriage return inside a
#comment on theversion:line: BecauseFIRST_LINE_VERSIONuses themflag,$matches before a bare\r, whereas the YAML parser only ends a#comment at\n. Inversion: "0.2.0" #\r.dev0",FIRST_LINE_VERSIONreads"0.2.0"while the YAML parser swallows#\r.dev0"into the comment on line 1 with no error on that line.
Here is a test highlighting the problem:
test('reports the YAML error when the version line has an unspaced comment quote, mismatched quote, or carriage return in a comment', () => {
// Setup: broken documents where the `version:` line has an unclosed quote
// closed by `#"` or `'"` on the same line (paired with an unclosed quote
// inside `semantic_model`), or a lone `\r` after `#` hiding `.dev0"`.
for (const text of [
`
version: "0.2.0.dev0#"
semantic_model:
- name: m
description: "
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
`
version: '0.2.0.dev0#'
semantic_model:
- name: m
description: '
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
`
version: "0.2.0.dev0'"
semantic_model:
- name: m
description: '
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
`
version: "0.2.0" #\r.dev0"
semantic_model:
- name: m
description: "
custom_extensions:
- vendor_name: GOOGLE
data: '{"profiles": [{"name": "prod"}]}'
`,
]) {
expect(() => loadModels(text)).toThrow('could not parse input');
// Current behavior on this PR: `googleBlockProfiles(text)` returns `[]`
// because `FIRST_LINE_VERSION` allows `#` and `'` inside double-quoted
// versions (`"` inside single-quoted versions) and matches `$` before a
// lone `\r` under the `m` flag.
// Expected behavior: throws `Semantic model load error: could not parse
// input: ...` because fixing the YAML error yields a vanilla model with
// profile `prod`.
expect(() => googleBlockProfiles(text)).toThrow(
'Semantic model load error: could not parse input: ');
}
});There was a problem hiding this comment.
The same change fixes these, since the first-line expression is gone. All four files now report the YAML error, and they are in the same test.
…s set twice A YAML error other than a key set twice could still change the version that the loader read from the first line. An error on the next root key's value or on a later key passed the checks. So did a version quote closed after a `#` or by the other quote, and a comment that runs past a lone carriage return. The loader now reads the version past a YAML error only when every error is a key set twice. The first-line expression and its next-key, word-count and backslash checks are removed. Other broken files change too. A Google-flavor model with any other YAML error now gets the YAML error from the block profile lookup. `kcmd profiles` prints that error alone instead of listing each profile with it, and `kcmd push --profile prod` prints it instead of saying the model defines no profiles. A vanilla model with such an error and a profile file beside it gets the YAML error instead of the error about profile files beside a vanilla model.
When a model file is not valid YAML, kcmd still reads its
version, because the version decides whether kcmd reads the profiles in the model's GOOGLE blocks and whether profile files may sit beside the model.Past a YAML error other than a key set twice, the loader read the version from the first line when that line set it alone. A YAML error elsewhere in the file could still change that version. For example,
version: "0.2.0"followed by.dev0"read as version 0.2.0. A vanilla model read this way gave no block profiles, sokcmd push --profile prodsaid the model defines no profiles.The loader now reads the version past a YAML error only when every error is a key set twice. Otherwise each command reports the YAML error.
Other broken files get the YAML error too. A Google-flavor model with a YAML syntax error gets it from
kcmd profilesandkcmd push --profile <name>before any profile is listed or applied. A vanilla model with a YAML syntax error and a profile file beside it gets it instead of the error about profile files beside a vanilla model.loader.ts: the version is read past a YAML error only when every error is a key set twice.loader.test.ts: broken version lines that the first-line rule read wrongly.semantic_model_layout.test.tsandcommands.test.ts: a vanilla model with a YAML syntax error no longer counts as vanilla.