diff --git a/lib/analysis-paths.test.js b/lib/analysis-paths.test.js new file mode 100644 index 0000000000..0ac02924fc --- /dev/null +++ b/lib/analysis-paths.test.js @@ -0,0 +1,29 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = __importDefault(require("ava")); +const analysisPaths = __importStar(require("./analysis-paths")); +const configUtils = __importStar(require("./config-utils")); +ava_1.default("emptyPaths", async (t) => { + let config = new configUtils.Config(); + analysisPaths.includeAndExcludeAnalysisPaths(config, []); + t.is(process.env['LGTM_INDEX_INCLUDE'], undefined); + t.is(process.env['LGTM_INDEX_EXCLUDE'], undefined); +}); +ava_1.default("nonEmptyPaths", async (t) => { + let config = new configUtils.Config(); + config.paths.push('path1', 'path2'); + config.pathsIgnore.push('path3', 'path4'); + analysisPaths.includeAndExcludeAnalysisPaths(config, []); + t.is(process.env['LGTM_INDEX_INCLUDE'], 'path1\npath2'); + t.is(process.env['LGTM_INDEX_EXCLUDE'], 'path3\npath4'); +}); diff --git a/lib/external-queries.test.js b/lib/external-queries.test.js new file mode 100644 index 0000000000..d3544bc4b7 --- /dev/null +++ b/lib/external-queries.test.js @@ -0,0 +1,30 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = __importDefault(require("ava")); +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +const configUtils = __importStar(require("./config-utils")); +const externalQueries = __importStar(require("./external-queries")); +const util = __importStar(require("./util")); +ava_1.default("checkoutExternalQueries", async (t) => { + let config = new configUtils.Config(); + config.externalQueries = [ + new configUtils.ExternalQuery("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b"), + ]; + await util.withTmpDir(async (tmpDir) => { + process.env["RUNNER_WORKSPACE"] = tmpDir; + await externalQueries.checkoutExternalQueries(config); + // COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master + t.true(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT"))); + }); +}); diff --git a/lib/fingerprints.test.js b/lib/fingerprints.test.js new file mode 100644 index 0000000000..b6b0d0601d --- /dev/null +++ b/lib/fingerprints.test.js @@ -0,0 +1,156 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = __importDefault(require("ava")); +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +const fingerprints = __importStar(require("./fingerprints")); +function testHash(t, input, expectedHashes) { + let index = 0; + let callback = function (lineNumber, hash) { + t.is(lineNumber, index + 1); + t.is(hash, expectedHashes[index]); + index++; + }; + fingerprints.hash(callback, input); + t.is(index, input.split(/\r\n|\r|\n/).length); +} +ava_1.default('hash', (t) => { + // Try empty file + testHash(t, "", ["c129715d7a2bc9a3:1"]); + // Try various combinations of newline characters + testHash(t, " a\nb\n \t\tc\n d", [ + "271789c17abda88f:1", + "54703d4cd895b18:1", + "180aee12dab6264:1", + "a23a3dc5e078b07b:1" + ]); + testHash(t, " hello; \t\nworld!!!\n\n\n \t\tGreetings\n End", [ + "8b7cf3e952e7aeb2:1", + "b1ae1287ec4718d9:1", + "bff680108adb0fcc:1", + "c6805c5e1288b612:1", + "b86d3392aea1be30:1", + "e6ceba753e1a442:1", + ]); + testHash(t, " hello; \t\nworld!!!\n\n\n \t\tGreetings\n End\n", [ + "e9496ae3ebfced30:1", + "fb7c023a8b9ccb3f:1", + "ce8ba1a563dcdaca:1", + "e20e36e16fcb0cc8:1", + "b3edc88f2938467e:1", + "c8e28b0b4002a3a0:1", + "c129715d7a2bc9a3:1", + ]); + testHash(t, " hello; \t\nworld!!!\r\r\r \t\tGreetings\r End\r", [ + "e9496ae3ebfced30:1", + "fb7c023a8b9ccb3f:1", + "ce8ba1a563dcdaca:1", + "e20e36e16fcb0cc8:1", + "b3edc88f2938467e:1", + "c8e28b0b4002a3a0:1", + "c129715d7a2bc9a3:1", + ]); + testHash(t, " hello; \t\r\nworld!!!\r\n\r\n\r\n \t\tGreetings\r\n End\r\n", [ + "e9496ae3ebfced30:1", + "fb7c023a8b9ccb3f:1", + "ce8ba1a563dcdaca:1", + "e20e36e16fcb0cc8:1", + "b3edc88f2938467e:1", + "c8e28b0b4002a3a0:1", + "c129715d7a2bc9a3:1", + ]); + testHash(t, " hello; \t\nworld!!!\r\n\n\r \t\tGreetings\r End\r\n", [ + "e9496ae3ebfced30:1", + "fb7c023a8b9ccb3f:1", + "ce8ba1a563dcdaca:1", + "e20e36e16fcb0cc8:1", + "b3edc88f2938467e:1", + "c8e28b0b4002a3a0:1", + "c129715d7a2bc9a3:1", + ]); + // Try repeating line that will generate identical hashes + testHash(t, "Lorem ipsum dolor sit amet.\n".repeat(10), [ + "a7f2ff13bc495cf2:1", + "a7f2ff13bc495cf2:2", + "a7f2ff13bc495cf2:3", + "a7f2ff13bc495cf2:4", + "a7f2ff13bc495cf2:5", + "a7f2ff13bc495cf2:6", + "a7f2ff1481e87703:1", + "a9cf91f7bbf1862b:1", + "55ec222b86bcae53:1", + "cc97dc7b1d7d8f7b:1", + "c129715d7a2bc9a3:1" + ]); +}); +function testResolveUriToFile(uri, index, artifactsURIs) { + const location = { "uri": uri, "index": index }; + const artifacts = artifactsURIs.map(uri => ({ "location": { "uri": uri } })); + return fingerprints.resolveUriToFile(location, artifacts); +} +ava_1.default('resolveUriToFile', t => { + // The resolveUriToFile method checks that the file exists and is in the right directory + // so we need to give it real files to look at. We will use this file as an example. + // For this to work we require the current working directory to be a parent, but this + // should generally always be the case so this is fine. + const cwd = process.cwd(); + const filepath = __filename; + t.true(filepath.startsWith(cwd + '/')); + const relativeFilepaht = filepath.substring(cwd.length + 1); + process.env['GITHUB_WORKSPACE'] = cwd; + // Absolute paths are unmodified + t.is(testResolveUriToFile(filepath, undefined, []), filepath); + t.is(testResolveUriToFile('file://' + filepath, undefined, []), filepath); + // Relative paths are made absolute + t.is(testResolveUriToFile(relativeFilepaht, undefined, []), filepath); + t.is(testResolveUriToFile('file://' + relativeFilepaht, undefined, []), filepath); + // Absolute paths outside the src root are discarded + t.is(testResolveUriToFile('/src/foo/bar.js', undefined, []), undefined); + t.is(testResolveUriToFile('file:///src/foo/bar.js', undefined, []), undefined); + // Other schemes are discarded + t.is(testResolveUriToFile('https://' + filepath, undefined, []), undefined); + t.is(testResolveUriToFile('ftp://' + filepath, undefined, []), undefined); + // Invalid URIs are discarded + t.is(testResolveUriToFile(1, undefined, []), undefined); + t.is(testResolveUriToFile(undefined, undefined, []), undefined); + // Non-existant files are discarded + t.is(testResolveUriToFile(filepath + '2', undefined, []), undefined); + // Index is resolved + t.is(testResolveUriToFile(undefined, 0, [filepath]), filepath); + t.is(testResolveUriToFile(undefined, 1, ['foo', filepath]), filepath); + // Invalid indexes are discarded + t.is(testResolveUriToFile(undefined, 1, [filepath]), undefined); + t.is(testResolveUriToFile(undefined, '0', [filepath]), undefined); +}); +ava_1.default('addFingerprints', t => { + // Run an end-to-end test on a test file + let input = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting.input.sarif').toString(); + let expected = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting.expected.sarif').toString(); + // The test files are stored prettified, but addFingerprints outputs condensed JSON + input = JSON.stringify(JSON.parse(input)); + expected = JSON.stringify(JSON.parse(expected)); + // The URIs in the SARIF files resolve to files in the testdata directory + process.env['GITHUB_WORKSPACE'] = path.normalize(__dirname + '/../src/testdata'); + t.deepEqual(fingerprints.addFingerprints(input), expected); +}); +ava_1.default('missingRegions', t => { + // Run an end-to-end test on a test file + let input = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting2.input.sarif').toString(); + let expected = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting2.expected.sarif').toString(); + // The test files are stored prettified, but addFingerprints outputs condensed JSON + input = JSON.stringify(JSON.parse(input)); + expected = JSON.stringify(JSON.parse(expected)); + // The URIs in the SARIF files resolve to files in the testdata directory + process.env['GITHUB_WORKSPACE'] = path.normalize(__dirname + '/../src/testdata'); + t.deepEqual(fingerprints.addFingerprints(input), expected); +}); diff --git a/lib/util.test.js b/lib/util.test.js new file mode 100644 index 0000000000..5358059497 --- /dev/null +++ b/lib/util.test.js @@ -0,0 +1,20 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = __importDefault(require("ava")); +const fs = __importStar(require("fs")); +const util = __importStar(require("./util")); +ava_1.default('getToolNames', t => { + const input = fs.readFileSync(__dirname + '/../src/testdata/tool-names.sarif', 'utf8'); + const toolNames = util.getToolNames(input); + t.deepEqual(toolNames, ["CodeQL command-line toolchain", "ESLint"]); +}); diff --git a/package.json b/package.json index b5f067159a..84b645ef1d 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,17 @@ "description": "CodeQL action", "scripts": { "build": "tsc", - "test": "jest", + "test": "ava src/**", "lint": "tslint -p . -c tslint.json 'src/**/*.ts'", "removeNPMAbsolutePaths": "removeNPMAbsolutePaths . --force" }, + "ava": { + "typescript": { + "rewritePaths": { + "src/": "lib/" + } + } + }, "license": "MIT", "dependencies": { "@actions/core": "^1.0.0", diff --git a/src/analysis-paths.test.ts b/src/analysis-paths.test.ts index 890f75d73c..22a21abc9c 100644 --- a/src/analysis-paths.test.ts +++ b/src/analysis-paths.test.ts @@ -1,18 +1,20 @@ +import test from 'ava'; + import * as analysisPaths from './analysis-paths'; import * as configUtils from './config-utils'; -test("emptyPaths", async () => { +test("emptyPaths", async t => { let config = new configUtils.Config(); analysisPaths.includeAndExcludeAnalysisPaths(config, []); - expect(process.env['LGTM_INDEX_INCLUDE']).toBeUndefined(); - expect(process.env['LGTM_INDEX_EXCLUDE']).toBeUndefined(); + t.is(process.env['LGTM_INDEX_INCLUDE'], undefined); + t.is(process.env['LGTM_INDEX_EXCLUDE'], undefined); }); -test("nonEmptyPaths", async () => { +test("nonEmptyPaths", async t => { let config = new configUtils.Config(); config.paths.push('path1', 'path2'); config.pathsIgnore.push('path3', 'path4'); analysisPaths.includeAndExcludeAnalysisPaths(config, []); - expect(process.env['LGTM_INDEX_INCLUDE']).toEqual('path1\npath2'); - expect(process.env['LGTM_INDEX_EXCLUDE']).toEqual('path3\npath4'); -}); \ No newline at end of file + t.is(process.env['LGTM_INDEX_INCLUDE'], 'path1\npath2'); + t.is(process.env['LGTM_INDEX_EXCLUDE'], 'path3\npath4'); +}); diff --git a/src/external-queries.test.ts b/src/external-queries.test.ts index a79f3f3e5c..c381a58e7c 100644 --- a/src/external-queries.test.ts +++ b/src/external-queries.test.ts @@ -1,3 +1,4 @@ +import test from 'ava'; import * as fs from "fs"; import * as path from "path"; @@ -5,7 +6,7 @@ import * as configUtils from "./config-utils"; import * as externalQueries from "./external-queries"; import * as util from "./util"; -test("checkoutExternalQueries", async () => { +test("checkoutExternalQueries", async t => { let config = new configUtils.Config(); config.externalQueries = [ new configUtils.ExternalQuery("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b"), @@ -16,6 +17,6 @@ test("checkoutExternalQueries", async () => { await externalQueries.checkoutExternalQueries(config); // COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master - expect(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT"))).toBeTruthy(); + t.true(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT"))); }); }); diff --git a/src/fingerprints.test.ts b/src/fingerprints.test.ts index 5d3f6605da..327bb1dd31 100644 --- a/src/fingerprints.test.ts +++ b/src/fingerprints.test.ts @@ -1,24 +1,28 @@ +import test from 'ava'; +import * as ava from "ava"; import * as fs from 'fs'; +import * as path from 'path'; import * as fingerprints from './fingerprints'; -function testHash(input: string, expectedHashes: string[]) { +function testHash(t: ava.Assertions, input: string, expectedHashes: string[]) { let index = 0; let callback = function (lineNumber: number, hash: string) { - expect(lineNumber).toBe(index + 1); - expect(hash).toBe(expectedHashes[index]); + t.is(lineNumber, index + 1); + t.is(hash, expectedHashes[index]); index++; }; fingerprints.hash(callback, input); - expect(index).toBe(input.split(/\r\n|\r|\n/).length); + t.is(index, input.split(/\r\n|\r|\n/).length); } -test('hash', () => { +test('hash', (t: ava.Assertions) => { // Try empty file - testHash("", ["c129715d7a2bc9a3:1"]); + testHash(t, "", ["c129715d7a2bc9a3:1"]); // Try various combinations of newline characters testHash( + t, " a\nb\n \t\tc\n d", [ "271789c17abda88f:1", @@ -27,6 +31,7 @@ test('hash', () => { "a23a3dc5e078b07b:1" ]); testHash( + t, " hello; \t\nworld!!!\n\n\n \t\tGreetings\n End", [ "8b7cf3e952e7aeb2:1", @@ -37,6 +42,7 @@ test('hash', () => { "e6ceba753e1a442:1", ]); testHash( + t, " hello; \t\nworld!!!\n\n\n \t\tGreetings\n End\n", [ "e9496ae3ebfced30:1", @@ -48,6 +54,7 @@ test('hash', () => { "c129715d7a2bc9a3:1", ]); testHash( + t, " hello; \t\nworld!!!\r\r\r \t\tGreetings\r End\r", [ "e9496ae3ebfced30:1", @@ -59,6 +66,7 @@ test('hash', () => { "c129715d7a2bc9a3:1", ]); testHash( + t, " hello; \t\r\nworld!!!\r\n\r\n\r\n \t\tGreetings\r\n End\r\n", [ "e9496ae3ebfced30:1", @@ -70,6 +78,7 @@ test('hash', () => { "c129715d7a2bc9a3:1", ]); testHash( + t, " hello; \t\nworld!!!\r\n\n\r \t\tGreetings\r End\r\n", [ "e9496ae3ebfced30:1", @@ -83,6 +92,7 @@ test('hash', () => { // Try repeating line that will generate identical hashes testHash( + t, "Lorem ipsum dolor sit amet.\n".repeat(10), [ "a7f2ff13bc495cf2:1", @@ -105,76 +115,76 @@ function testResolveUriToFile(uri: any, index: any, artifactsURIs: any[]) { return fingerprints.resolveUriToFile(location, artifacts); } -test('resolveUriToFile', () => { +test('resolveUriToFile', t => { // The resolveUriToFile method checks that the file exists and is in the right directory // so we need to give it real files to look at. We will use this file as an example. // For this to work we require the current working directory to be a parent, but this // should generally always be the case so this is fine. const cwd = process.cwd(); const filepath = __filename; - expect(filepath.startsWith(cwd + '/')).toBeTruthy(); + t.true(filepath.startsWith(cwd + '/')); const relativeFilepaht = filepath.substring(cwd.length + 1); process.env['GITHUB_WORKSPACE'] = cwd; // Absolute paths are unmodified - expect(testResolveUriToFile(filepath, undefined, [])).toBe(filepath); - expect(testResolveUriToFile('file://' + filepath, undefined, [])).toBe(filepath); + t.is(testResolveUriToFile(filepath, undefined, []), filepath); + t.is(testResolveUriToFile('file://' + filepath, undefined, []), filepath); // Relative paths are made absolute - expect(testResolveUriToFile(relativeFilepaht, undefined, [])).toBe(filepath); - expect(testResolveUriToFile('file://' + relativeFilepaht, undefined, [])).toBe(filepath); + t.is(testResolveUriToFile(relativeFilepaht, undefined, []), filepath); + t.is(testResolveUriToFile('file://' + relativeFilepaht, undefined, []), filepath); // Absolute paths outside the src root are discarded - expect(testResolveUriToFile('/src/foo/bar.js', undefined, [])).toBe(undefined); - expect(testResolveUriToFile('file:///src/foo/bar.js', undefined, [])).toBe(undefined); + t.is(testResolveUriToFile('/src/foo/bar.js', undefined, []), undefined); + t.is(testResolveUriToFile('file:///src/foo/bar.js', undefined, []), undefined); // Other schemes are discarded - expect(testResolveUriToFile('https://' + filepath, undefined, [])).toBe(undefined); - expect(testResolveUriToFile('ftp://' + filepath, undefined, [])).toBe(undefined); + t.is(testResolveUriToFile('https://' + filepath, undefined, []), undefined); + t.is(testResolveUriToFile('ftp://' + filepath, undefined, []), undefined); // Invalid URIs are discarded - expect(testResolveUriToFile(1, undefined, [])).toBe(undefined); - expect(testResolveUriToFile(undefined, undefined, [])).toBe(undefined); + t.is(testResolveUriToFile(1, undefined, []), undefined); + t.is(testResolveUriToFile(undefined, undefined, []), undefined); // Non-existant files are discarded - expect(testResolveUriToFile(filepath + '2', undefined, [])).toBe(undefined); + t.is(testResolveUriToFile(filepath + '2', undefined, []), undefined); // Index is resolved - expect(testResolveUriToFile(undefined, 0, [filepath])).toBe(filepath); - expect(testResolveUriToFile(undefined, 1, ['foo', filepath])).toBe(filepath); + t.is(testResolveUriToFile(undefined, 0, [filepath]), filepath); + t.is(testResolveUriToFile(undefined, 1, ['foo', filepath]), filepath); // Invalid indexes are discarded - expect(testResolveUriToFile(undefined, 1, [filepath])).toBe(undefined); - expect(testResolveUriToFile(undefined, '0', [filepath])).toBe(undefined); + t.is(testResolveUriToFile(undefined, 1, [filepath]), undefined); + t.is(testResolveUriToFile(undefined, '0', [filepath]), undefined); }); -test('addFingerprints', () => { +test('addFingerprints', t => { // Run an end-to-end test on a test file - let input = fs.readFileSync(__dirname + '/testdata/fingerprinting.input.sarif').toString(); - let expected = fs.readFileSync(__dirname + '/testdata/fingerprinting.expected.sarif').toString(); + let input = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting.input.sarif').toString(); + let expected = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting.expected.sarif').toString(); // The test files are stored prettified, but addFingerprints outputs condensed JSON input = JSON.stringify(JSON.parse(input)); expected = JSON.stringify(JSON.parse(expected)); // The URIs in the SARIF files resolve to files in the testdata directory - process.env['GITHUB_WORKSPACE'] = __dirname + '/testdata'; + process.env['GITHUB_WORKSPACE'] = path.normalize(__dirname + '/../src/testdata'); - expect(fingerprints.addFingerprints(input)).toBe(expected); + t.deepEqual(fingerprints.addFingerprints(input), expected); }); -test('missingRegions', () => { +test('missingRegions', t => { // Run an end-to-end test on a test file - let input = fs.readFileSync(__dirname + '/testdata/fingerprinting2.input.sarif').toString(); - let expected = fs.readFileSync(__dirname + '/testdata/fingerprinting2.expected.sarif').toString(); + let input = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting2.input.sarif').toString(); + let expected = fs.readFileSync(__dirname + '/../src/testdata/fingerprinting2.expected.sarif').toString(); // The test files are stored prettified, but addFingerprints outputs condensed JSON input = JSON.stringify(JSON.parse(input)); expected = JSON.stringify(JSON.parse(expected)); // The URIs in the SARIF files resolve to files in the testdata directory - process.env['GITHUB_WORKSPACE'] = __dirname + '/testdata'; + process.env['GITHUB_WORKSPACE'] = path.normalize(__dirname + '/../src/testdata'); - expect(fingerprints.addFingerprints(input)).toBe(expected); + t.deepEqual(fingerprints.addFingerprints(input), expected); }); diff --git a/src/util.test.ts b/src/util.test.ts index 3dfd2f72d9..e530b2aca2 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -1,9 +1,10 @@ +import test from 'ava'; import * as fs from 'fs'; import * as util from './util'; -test('getToolNames', () => { - const input = fs.readFileSync(__dirname + '/testdata/tool-names.sarif', 'utf8') +test('getToolNames', t => { + const input = fs.readFileSync(__dirname + '/../src/testdata/tool-names.sarif', 'utf8'); const toolNames = util.getToolNames(input); - expect(toolNames).toStrictEqual(["CodeQL command-line toolchain", "ESLint"]) -}) + t.deepEqual(toolNames, ["CodeQL command-line toolchain", "ESLint"]); +}); diff --git a/tsconfig.json b/tsconfig.json index f2826eb7ab..39945d77b9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -59,5 +59,5 @@ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, - "exclude": ["node_modules", "**/*.test.ts"] + "exclude": ["node_modules"] }