From 46968350a8ff7466a38ef329d89f31f7ce8be337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Tue, 21 Jan 2020 17:20:20 +0100 Subject: [PATCH] Support JSON Schema in determineScalarType --- src/scalarInference.ts | 58 ++++++++++++++-- test/scalarInference.test.ts | 128 +++++++++++++++++++++++++---------- 2 files changed, 145 insertions(+), 41 deletions(-) diff --git a/src/scalarInference.ts b/src/scalarInference.ts index 6b4543d..53cfbed 100644 --- a/src/scalarInference.ts +++ b/src/scalarInference.ts @@ -1,4 +1,5 @@ import { YAMLScalar } from './yamlAST' +import YAMLException = require('./exception'); export function parseYamlBoolean(input: string): boolean { if (["true", "True", "TRUE"].lastIndexOf(input) >= 0) { @@ -54,15 +55,60 @@ export enum ScalarType { null, bool, int, float, string } +export enum DetermineScalarSchema { + Core = 'core', + Json = 'json', +} + + +export function determineScalarType(node: YAMLScalar, schema: DetermineScalarSchema = DetermineScalarSchema.Core): ScalarType { + if (node === void 0) { + // we should throw here, but for backwards compatibility purposes we need to have it here + return ScalarType.null; + } + + switch (schema) { + case DetermineScalarSchema.Core: + return determineScalarTypeForCoreSchema(node); + case DetermineScalarSchema.Json: + return determineScalarTypeForJsonSchema(node); + } +} + /** Determines the type of a scalar according to - * the YAML 1.2 Core Schema (http://www.yaml.org/spec/1.2/spec.html#id2804923) - */ -export function determineScalarType(node: YAMLScalar): ScalarType { - if (node === undefined) { + * the YAML 1.2 JSON Schema (https://yaml.org/spec/1.2/spec.html#id2804356) + */ +export function determineScalarTypeForJsonSchema(node: YAMLScalar): ScalarType { + if (node.doubleQuoted || node.singleQuoted) { + return ScalarType.string + } + + const value = node.value; + + if (value === 'null') { return ScalarType.null; } - if (node.doubleQuoted || !node.plainScalar || node['singleQuoted']) { + if (value === 'true' || value === 'false') { + return ScalarType.bool; + } + + if (/^-?(?:0|[1-9][0-9]*)$/.test(value)) { + return ScalarType.int; + } + + if (/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/.test(value)) { + return ScalarType.float; + } + + throw new YAMLException('could not determine scalar type'); +} + +/** Determines the type of a scalar according to + * the YAML 1.2 Core Schema (http://www.yaml.org/spec/1.2/spec.html#id2804923) + */ +export function determineScalarTypeForCoreSchema(node: YAMLScalar): ScalarType { + if (node.doubleQuoted || !node.plainScalar || node.singleQuoted) { return ScalarType.string } @@ -95,4 +141,4 @@ export function determineScalarType(node: YAMLScalar): ScalarType { } return ScalarType.string; -} \ No newline at end of file +} diff --git a/test/scalarInference.test.ts b/test/scalarInference.test.ts index 6b7d93c..7dca39b 100644 --- a/test/scalarInference.test.ts +++ b/test/scalarInference.test.ts @@ -1,13 +1,13 @@ import * as chai from 'chai' const assert = chai.assert -import { determineScalarType as sut, ScalarType, parseYamlBoolean, parseYamlInteger, parseYamlFloat } from '../src/scalarInference' +import { DetermineScalarSchema, determineScalarType as sut, ScalarType, parseYamlBoolean, parseYamlInteger, parseYamlFloat } from '../src/scalarInference' import * as Yaml from '../src/index' suite('determineScalarType', () => { - function determineScalarType(scalar: Yaml.YAMLDocument) { - return sut(scalar) + function determineScalarType(scalar: Yaml.YAMLDocument, schema: DetermineScalarSchema = DetermineScalarSchema.Core) { + return sut(scalar, schema) } function safeLoad(input) { @@ -16,40 +16,97 @@ suite('determineScalarType', () => { let _test = test; - // http://www.yaml.org/spec/1.2/spec.html#id2805071 - suite('Plain Tag Resolution', () => { + suite('JSON Schema', () => { + suite('Plain Tag Resolution', () => { - function test(name, type, acceptable) { - _test(name, function () { - for (const word of acceptable) { - assert.strictEqual(determineScalarType(safeLoad(word)), type, word) - } + function test(name, type, acceptable) { + _test(name, function () { + for (const word of acceptable) { + assert.strictEqual(determineScalarType(safeLoad(word), DetermineScalarSchema.Json), type, word) + } + }) + } + + test('boolean', ScalarType.bool, ["true", "false"]) + + test("null", ScalarType.null, ["null"]) + + _test("null as from an array", function () { + const node = Yaml.newScalar(''); + node.plainScalar = true; + assert.strictEqual(determineScalarType(node), ScalarType.null, "unquoted empty string") }) - }; - test('boolean', ScalarType.bool, ["true", "True", "TRUE", "false", "False", "FALSE"]) + test("integer", ScalarType.int, ["0", "-0", "3", "-19"]) + + test("float", ScalarType.float, ["0.", "-0.0", "12e03", "-2E+05"]) - test("null", ScalarType.null, ["null", "Null", "NULL", "~", ""]) - _test("null as from an array", function () { - const node = Yaml.newScalar(''); - node.plainScalar = true; - assert.strictEqual(determineScalarType(node), ScalarType.null, "unquoted empty string") + test("string", ScalarType.string, ["'true'", "'TrUe'", "''", "'0'", '"1"', '" .5"']) }) - test("integer", ScalarType.int, ["0", "0o7", "0x3A", "-19"]) + suite('Flow style', () => { + test('still recognizes types', function () { + const node = safeLoad(`[ null, + true, + 0, + 0., + .inf, + .nan, + "-123\n345" +]`) - test("float", ScalarType.float, ["0.", "-0.0", ".5", "+12e03", "-2E+05"]) + const expected = [ScalarType.null, ScalarType.bool, ScalarType.int, ScalarType.float, ScalarType.float, ScalarType.float, ScalarType.string] - test("float-infinity", ScalarType.float, [".inf", "-.Inf", "+.INF"]) + assert.deepEqual(node.items.map(d => determineScalarType(d)), expected) + }) + }) - test("float-NaN", ScalarType.float, [".nan", ".NaN", ".NAN"]) + suite('Block styles', () => { + var variations = ['>', '|', '>8', '|+1', '>-', '>+', '|-', '|+'] - test("string", ScalarType.string, ["'true'", "TrUe", "nULl", "''", "'0'", '"1"', '" .5"', ".inF", ".nAn"]) + test('are always strings', function () { + for (const variant of variations) { + assert.deepEqual(determineScalarType(safeLoad(variant + "\n 123")), ScalarType.string); + } + }) + }) }) - suite('Flow style', () => { - test('still recognizes types', function () { - const node = safeLoad(`[ null, + // http://www.yaml.org/spec/1.2/spec.html#id2805071 + suite('Core Schema', () => { + suite('Plain Tag Resolution', () => { + + function test(name, type, acceptable) { + _test(name, function () { + for (const word of acceptable) { + assert.strictEqual(determineScalarType(safeLoad(word)), type, word) + } + }) + }; + + test('boolean', ScalarType.bool, ["true", "True", "TRUE", "false", "False", "FALSE"]) + + test("null", ScalarType.null, ["null", "Null", "NULL", "~", ""]) + _test("null as from an array", function () { + const node = Yaml.newScalar(''); + node.plainScalar = true; + assert.strictEqual(determineScalarType(node), ScalarType.null, "unquoted empty string") + }) + + test("integer", ScalarType.int, ["0", "0o7", "0x3A", "-19"]) + + test("float", ScalarType.float, ["0.", "-0.0", ".5", "+12e03", "-2E+05"]) + + test("float-infinity", ScalarType.float, [".inf", "-.Inf", "+.INF"]) + + test("float-NaN", ScalarType.float, [".nan", ".NaN", ".NAN"]) + + test("string", ScalarType.string, ["'true'", "TrUe", "nULl", "''", "'0'", '"1"', '" .5"', ".inF", ".nAn"]) + }) + + suite('Flow style', () => { + test('still recognizes types', function () { + const node = safeLoad(`[ null, true, 0, 0., @@ -58,19 +115,20 @@ suite('determineScalarType', () => { "-123\n345" ]`) - const expected = [ScalarType.null, ScalarType.bool, ScalarType.int, ScalarType.float, ScalarType.float, ScalarType.float, ScalarType.string] + const expected = [ScalarType.null, ScalarType.bool, ScalarType.int, ScalarType.float, ScalarType.float, ScalarType.float, ScalarType.string] - assert.deepEqual(node.items.map(d => determineScalarType(d)), expected) + assert.deepEqual(node.items.map(d => determineScalarType(d)), expected) + }) }) - }) - suite('Block styles', () => { - var variations = ['>', '|', '>8', '|+1', '>-', '>+', '|-', '|+'] + suite('Block styles', () => { + var variations = ['>', '|', '>8', '|+1', '>-', '>+', '|-', '|+'] - test('are always strings', function () { - for (const variant of variations) { - assert.deepEqual(determineScalarType(safeLoad(variant + "\n 123")), ScalarType.string); - } + test('are always strings', function () { + for (const variant of variations) { + assert.deepEqual(determineScalarType(safeLoad(variant + "\n 123")), ScalarType.string); + } + }) }) }) }) @@ -162,4 +220,4 @@ suite('parseYamlFloat', () => { assert(error, "should have thrown") }) -}) \ No newline at end of file +})