diff --git a/.changeset/lazy-trees-cheer.md b/.changeset/lazy-trees-cheer.md new file mode 100644 index 0000000000..f7f1f08cd2 --- /dev/null +++ b/.changeset/lazy-trees-cheer.md @@ -0,0 +1,11 @@ +--- +'@redocly/openapi-core': patch +'@redocly/cli': patch +--- + +Updated js-yaml from `4.2.0` to `5.2.1`. +Fixed an issue where strings that look like numbers with underscores (for example `'12_34'`) had quotation marks removed by the `bundle` command. +These strings stay quoted in the output. + +**Note**: YAML parsing is stricter: a multi-line flow collection whose closing bracket is not indented deeper than its parent key is now a parse error. +Parse errors are reported at the offending token instead of the end of the document. diff --git a/package-lock.json b/package-lock.json index 07c4329a5a..9ddbcbbfa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3047,13 +3047,6 @@ "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==", "dev": true }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json-pointer": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/@types/json-pointer/-/json-pointer-1.0.34.tgz", @@ -4875,6 +4868,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, "funding": [ { "type": "github", @@ -8922,14 +8916,13 @@ "colorette": "^1.2.0", "graphql": "^16.14.1", "js-levenshtein": "^1.1.6", - "js-yaml": "^4.2.0", + "js-yaml": "^5.2.1", "picomatch": "^4.0.4", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" }, "devDependencies": { "@types/js-levenshtein": "^1.1.0", - "@types/js-yaml": "^4.0.3", "@types/picomatch": "^4.0.2", "@types/pluralize": "^0.0.29", "json-schema-to-ts": "^3.1.0", @@ -8957,6 +8950,28 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "packages/core/node_modules/js-yaml": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.1.tgz", + "integrity": "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, "packages/core/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index c8c643b7f8..d13cd05116 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -59,14 +59,13 @@ "colorette": "^1.2.0", "graphql": "^16.14.1", "js-levenshtein": "^1.1.6", - "js-yaml": "^4.2.0", + "js-yaml": "^5.2.1", "picomatch": "^4.0.4", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" }, "devDependencies": { "@types/js-levenshtein": "^1.1.0", - "@types/js-yaml": "^4.0.3", "@types/picomatch": "^4.0.2", "@types/pluralize": "^0.0.29", "json-schema-to-ts": "^3.1.0", diff --git a/packages/core/src/__tests__/js-yaml.test.ts b/packages/core/src/__tests__/js-yaml.test.ts index 71925ea8ea..93770fe6eb 100644 --- a/packages/core/src/__tests__/js-yaml.test.ts +++ b/packages/core/src/__tests__/js-yaml.test.ts @@ -71,4 +71,16 @@ describe('js-yaml', () => { 'unacceptable kind of an object to dump [object Function]' ); }); + + it('should return a nullish value instead of throwing for empty or comment-only input', () => { + expect(parseYaml('')).toBeUndefined(); + expect(parseYaml(' \n ')).toBeUndefined(); + expect(parseYaml('# just a comment\n')).toBeNull(); + }); + + it('throws when the stream contains more than one document', () => { + expect(() => parseYaml('a: 1\n---\nb: 2\n')).toThrow( + 'expected a single document in the stream, but found more' + ); + }); }); diff --git a/packages/core/src/js-yaml/index.ts b/packages/core/src/js-yaml/index.ts index 1a0b31b998..ccd53ac367 100644 --- a/packages/core/src/js-yaml/index.ts +++ b/packages/core/src/js-yaml/index.ts @@ -1,14 +1,34 @@ -// TODO: add a type for "types" https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/js-yaml/index.d.ts -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { JSON_SCHEMA, types, load, dump, type LoadOptions, type DumpOptions } from 'js-yaml'; +import { + CORE_SCHEMA, + mergeTag, + binaryTag, + omapTag, + pairsTag, + setTag, + loadAll, + dump, + YAMLException, + type LoadOptions, + type DumpOptions, +} from 'js-yaml'; -const DEFAULT_SCHEMA_WITHOUT_TIMESTAMP = JSON_SCHEMA.extend({ - implicit: [types.merge], - explicit: [types.binary, types.omap, types.pairs, types.set], -}); +const DEFAULT_SCHEMA_WITHOUT_TIMESTAMP = CORE_SCHEMA.withTags( + mergeTag, + binaryTag, + omapTag, + pairsTag, + setTag +); -export const parseYaml = (str: string, opts?: LoadOptions): unknown => - load(str, { schema: DEFAULT_SCHEMA_WITHOUT_TIMESTAMP, ...opts }); +export const parseYaml = (str: string, opts?: LoadOptions): unknown => { + const documents = loadAll(str, { schema: DEFAULT_SCHEMA_WITHOUT_TIMESTAMP, ...opts }); + if (documents.length === 0) { + return str.trim() === '' ? undefined : null; + } + if (documents.length > 1) { + throw new YAMLException('expected a single document in the stream, but found more'); + } + return documents[0]; +}; export const stringifyYaml = (obj: any, opts?: DumpOptions): string => dump(obj, opts); diff --git a/packages/core/src/rules/common/__tests__/no-unresolved-refs.test.ts b/packages/core/src/rules/common/__tests__/no-unresolved-refs.test.ts index a0f79005d4..a00c8ff61a 100644 --- a/packages/core/src/rules/common/__tests__/no-unresolved-refs.test.ts +++ b/packages/core/src/rules/common/__tests__/no-unresolved-refs.test.ts @@ -85,12 +85,12 @@ describe('oas3 boolean-parameter-prefixes', () => { "reportOnKey": false, "source": "fixtures/invalid-yaml.yaml", "start": { - "col": 1, - "line": 2, + "col": 8, + "line": 1, }, }, ], - "message": "Failed to parse: unexpected end of the stream within a single quoted scalar in "fixtures/invalid-yaml.yaml" (2:1)", + "message": "Failed to parse: unexpected end of the stream within a single quoted scalar in "fixtures/invalid-yaml.yaml" (1:8)", "ruleId": "no-unresolved-refs", "severity": "error", "suggest": [], @@ -103,7 +103,7 @@ describe('oas3 boolean-parameter-prefixes', () => { "source": "foobar.yaml", }, ], - "message": "Can't resolve $ref: unexpected end of the stream within a single quoted scalar in "fixtures/invalid-yaml.yaml" (2:1)", + "message": "Can't resolve $ref: unexpected end of the stream within a single quoted scalar in "fixtures/invalid-yaml.yaml" (1:8)", "reference": "https://redocly.com/docs/cli/rules/oas/no-unresolved-refs", "ruleId": "no-unresolved-refs", "severity": "error", diff --git a/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts b/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts index 4bc4677c53..f1959b3d89 100644 --- a/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +++ b/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts @@ -635,7 +635,7 @@ describe('no-invalid-media-type-examples', () => { example: { "a": "test", "b": "test" - } + } schema: $ref: '#/components/schemas/C' diff --git a/tests/e2e/bundle/primitive-types/openapi.yaml b/tests/e2e/bundle/primitive-types/openapi.yaml index 1e7cb6bf50..92d6cf1de8 100644 --- a/tests/e2e/bundle/primitive-types/openapi.yaml +++ b/tests/e2e/bundle/primitive-types/openapi.yaml @@ -37,3 +37,5 @@ paths: object: key1: 1 key2: 2 + underscoreNumberTransformedToString: 12_34 + underscoreNumberString: '12_34' diff --git a/tests/e2e/bundle/primitive-types/snapshot.txt b/tests/e2e/bundle/primitive-types/snapshot.txt index b1b5f89da5..a49ba63548 100644 --- a/tests/e2e/bundle/primitive-types/snapshot.txt +++ b/tests/e2e/bundle/primitive-types/snapshot.txt @@ -35,6 +35,8 @@ paths: object: key1: 1 key2: 2 + underscoreNumberTransformedToString: '12_34' + underscoreNumberString: '12_34' components: {} bundling openapi.yaml using configuration for api 'main'...