diff --git a/cli/vm/compile.ts b/cli/vm/compile.ts index e42a83aaa..f5e8cfc1e 100644 --- a/cli/vm/compile.ts +++ b/cli/vm/compile.ts @@ -48,7 +48,7 @@ export function compile(compileConfig: dataform.ICompileConfig) { resolve: (moduleName, parentDirName) => path.join(parentDirName, path.relative(parentDirName, compileConfig.projectDir), moduleName) }, - sourceExtensions: ["js", "sql", "sqlx", "yaml"], + sourceExtensions: ["js", "sql", "sqlx", "yaml", "yml"], compiler }); diff --git a/core/BUILD b/core/BUILD index 3655099e1..b46881a57 100644 --- a/core/BUILD +++ b/core/BUILD @@ -65,6 +65,7 @@ ts_test_suite( srcs = [ "main_test.ts", "utils_test.ts", + "compilers_test.ts", "actions/assertion_test.ts", "actions/data_preparation_test.ts", "actions/declaration_test.ts", diff --git a/core/compilers.ts b/core/compilers.ts index 69c8f7704..a760153ff 100644 --- a/core/compilers.ts +++ b/core/compilers.ts @@ -28,7 +28,7 @@ export function compile(code: string, path: string): string { if (Path.fileExtension(path) === "sqlx") { return compileSqlx(SyntaxTreeNode.create(code), path); } - if (Path.fileExtension(path) === "yaml") { + if (Path.fileExtension(path) === "yaml" || Path.fileExtension(path) === "yml") { try { const yamlAsJson = loadYaml(code); return `exports.asJson = ${JSON.stringify(yamlAsJson)}`; diff --git a/core/compilers_test.ts b/core/compilers_test.ts new file mode 100644 index 000000000..f5410e557 --- /dev/null +++ b/core/compilers_test.ts @@ -0,0 +1,96 @@ +import { expect } from "chai"; + +import { compile } from "df/core/compilers"; +import { suite, test } from "df/testing"; + +suite("core/compilers", () => { + suite("compile", () => { + test("compiles sqlx to js", () => { + const code = `config { type: "table" } select 1`; + const path = "definitions/foo.sqlx"; + const result = compile(code, path); + expect(result).to.include("dataform.sqlxAction"); + expect(result).to.include("name: \"foo\""); + expect(result).to.include("{ type: \"table\" }"); + // The sqlx compiler will format the query. + expect(result).to.include("select 1"); + }); + + test("compiles yml to js", () => { + const code = "foo: bar"; + const path = "definitions/foo.yml"; + const result = compile(code, path); + expect(result).to.equal(`exports.asJson = {"foo":"bar"}`); + }); + + test("compiles yaml to js", () => { + const code = ` +- item1 +- item2`; + const path = "definitions/foo.yaml"; + const result = compile(code, path); + expect(result).to.equal(`exports.asJson = ["item1","item2"]`); + }); + + test("throws error for invalid yaml", () => { + const code = "foo: : bar"; + const path = "definitions/foo.yaml"; + expect(() => compile(code, path)).to.throw(`${path} is not a valid YAML file: YAMLException: bad indentation of a mapping entry (1:6)`); + }); + + test("compiles ipynb to js", () => { + const code = '{"cells": []}'; + const path = "definitions/foo.ipynb"; + const result = compile(code, path); + expect(result).to.equal('exports.asJson = {"cells":[]}'); + }); + + test("throws error for invalid ipynb json", () => { + const code = '{"cells": [}'; + const path = "definitions/foo.ipynb"; + expect(() => compile(code, path)).to.throw(`Error parsing ${path} as JSON:`); + }); + + test("compiles sql to js", () => { + const code = "select 1"; + const path = "definitions/foo.sql"; + const result = compile(code, path); + expect(result).to.equal("exports.query = `select 1`;"); + }); + + test("escapes backticks in sql", () => { + const code = "select `a` from `b`"; + const path = "definitions/foo.sql"; + const result = compile(code, path); + expect(result).to.equal("exports.query = `select \\`a\\` from \\`b\\``;"); + }); + + test("escapes backslashes in sql", () => { + const code = "select ''"; + const path = "definitions/foo.sql"; + const result = compile(code, path); + expect(result).to.equal("exports.query = `select ''`;"); + }); + + test("escapes template literals in sql", () => { + const code = "select ${foo}"; + const path = "definitions/foo.sql"; + const result = compile(code, path); + expect(result).to.equal("exports.query = `select \\${foo}`;"); + }); + + test("returns raw code for other file types", () => { + const code = "const a = 1;"; + const path = "definitions/foo.js"; + const result = compile(code, path); + expect(result).to.equal(code); + }); + + test("returns raw code for files with no extension", () => { + const code = "const a = 1;"; + const path = "definitions/foo"; + const result = compile(code, path); + expect(result).to.equal(code); + }); + }); +});