diff --git a/.eslintrc.json b/.eslintrc.json index 7bea33e..851ab42 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,15 @@ "sourceType": "module", "ecmaVersion": 2020 }, + "settings": { + "jsdoc": { + "mode": "typescript", + "preferredTypes": { + "Object": "object", + "object<>": "Object" + } + } + }, "overrides": [ { "files": ["*.cjs"], diff --git a/lib/index.js b/lib/index.js index 44f73d6..5222cf1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,6 +4,11 @@ */ import KEYS from "./visitor-keys.js"; +/** + * @typedef {{ [type: string]: ReadonlyArray }} KeysStrict + * @typedef {{ readonly [type: string]: ReadonlyArray }} KeysStrictReadonly + */ + // List to ignore keys. const KEY_BLACKLIST = new Set([ "parent", @@ -22,8 +27,8 @@ function filterKey(key) { /** * Get visitor keys of a given node. - * @param {Object} node The AST node to get keys. - * @returns {string[]} Visitor keys of the node. + * @param {object} node The AST node to get keys. + * @returns {readonly string[]} Visitor keys of the node. */ export function getKeys(node) { return Object.keys(node).filter(filterKey); @@ -33,11 +38,11 @@ export function getKeys(node) { // eslint-disable-next-line valid-jsdoc /** * Make the union set with `KEYS` and given keys. - * @param {Object} additionalKeys The additional keys. - * @returns {{ [type: string]: string[] | undefined }} The union set. + * @param {KeysStrictReadonly} additionalKeys The additional keys. + * @returns {KeysStrictReadonly} The union set. */ export function unionWith(additionalKeys) { - const retv = Object.assign({}, KEYS); + const retv = /** @type {KeysStrict} */ (Object.assign({}, KEYS)); for (const type of Object.keys(additionalKeys)) { if (Object.prototype.hasOwnProperty.call(retv, type)) { diff --git a/lib/visitor-keys.js b/lib/visitor-keys.js index 4c7be3d..286b903 100644 --- a/lib/visitor-keys.js +++ b/lib/visitor-keys.js @@ -1,4 +1,21 @@ +/** + * @typedef {import('./index.js').KeysStrictReadonly} KeysStrictReadonly + */ + +/** + * @type {KeysStrictReadonly} + */ const KEYS = { + ArrayExpression: [ + "elements" + ], + ArrayPattern: [ + "elements" + ], + ArrowFunctionExpression: [ + "body", + "params" + ], AssignmentExpression: [ "left", "right" @@ -7,36 +24,27 @@ const KEYS = { "left", "right" ], - ArrayExpression: [ - "elements" - ], - ArrayPattern: [ - "elements" - ], - ArrowFunctionExpression: [ - "params", - "body" + AssignmentProperty: [ + "key", + "value" ], AwaitExpression: [ "argument" ], - BlockStatement: [ - "body" - ], + BigIntLiteral: [], BinaryExpression: [ "left", "right" ], + BlockStatement: [ + "body" + ], BreakStatement: [ "label" ], - CallExpression: [ - "callee", - "arguments" - ], CatchClause: [ - "param", - "body" + "body", + "param" ], ChainExpression: [ "expression" @@ -45,24 +53,27 @@ const KEYS = { "body" ], ClassDeclaration: [ + "body", "id", - "superClass", - "body" + "superClass" ], ClassExpression: [ + "body", "id", - "superClass", - "body" + "superClass" ], ConditionalExpression: [ - "test", + "alternate", "consequent", - "alternate" + "test" ], ContinueStatement: [ "label" ], DebuggerStatement: [], + Directive: [ + "expression" + ], DoWhileStatement: [ "body", "test" @@ -87,43 +98,37 @@ const KEYS = { ExpressionStatement: [ "expression" ], - ExperimentalRestProperty: [ - "argument" - ], - ExperimentalSpreadProperty: [ - "argument" - ], - ForStatement: [ - "init", - "test", - "update", - "body" - ], ForInStatement: [ + "body", "left", - "right", - "body" + "right" ], ForOfStatement: [ + "body", "left", - "right", - "body" + "right" + ], + ForStatement: [ + "body", + "init", + "test", + "update" ], FunctionDeclaration: [ + "body", "id", - "params", - "body" + "params" ], FunctionExpression: [ + "body", "id", - "params", - "body" + "params" ], Identifier: [], IfStatement: [ - "test", + "alternate", "consequent", - "alternate" + "test" ], ImportDeclaration: [ "specifiers", @@ -146,18 +151,27 @@ const KEYS = { "name", "value" ], + JSXBoundaryElement: [ + "name" + ], JSXClosingElement: [ "name" ], + JSXClosingFragment: [], JSXElement: [ - "openingElement", "children", - "closingElement" + "closingElement", + "openingElement" ], JSXEmptyExpression: [], JSXExpressionContainer: [ "expression" ], + JSXFragment: [ + "children", + "closingFragment", + "openingFragment" + ], JSXIdentifier: [], JSXMemberExpression: [ "object", @@ -168,24 +182,20 @@ const KEYS = { "name" ], JSXOpeningElement: [ - "name", - "attributes" + "attributes", + "name" ], + JSXOpeningFragment: [], JSXSpreadAttribute: [ "argument" ], - JSXText: [], - JSXFragment: [ - "openingFragment", - "children", - "closingFragment" + JSXSpreadChild: [ + "expression" ], - JSXClosingFragment: [], - JSXOpeningFragment: [], - Literal: [], + JSXText: [], LabeledStatement: [ - "label", - "body" + "body", + "label" ], LogicalExpression: [ "left", @@ -204,8 +214,8 @@ const KEYS = { "value" ], NewExpression: [ - "callee", - "arguments" + "arguments", + "callee" ], ObjectExpression: [ "properties" @@ -225,6 +235,7 @@ const KEYS = { "key", "value" ], + RegExpLiteral: [], RestElement: [ "argument" ], @@ -234,6 +245,11 @@ const KEYS = { SequenceExpression: [ "expressions" ], + SimpleCallExpression: [ + "arguments", + "callee" + ], + SimpleLiteral: [], SpreadElement: [ "argument" ], @@ -241,22 +257,22 @@ const KEYS = { "body" ], Super: [], - SwitchStatement: [ - "discriminant", - "cases" - ], SwitchCase: [ - "test", - "consequent" + "consequent", + "test" + ], + SwitchStatement: [ + "cases", + "discriminant" ], TaggedTemplateExpression: [ - "tag", - "quasi" + "quasi", + "tag" ], TemplateElement: [], TemplateLiteral: [ - "quasis", - "expressions" + "expressions", + "quasis" ], ThisExpression: [], ThrowStatement: [ @@ -264,8 +280,8 @@ const KEYS = { ], TryStatement: [ "block", - "handler", - "finalizer" + "finalizer", + "handler" ], UnaryExpression: [ "argument" @@ -281,12 +297,12 @@ const KEYS = { "init" ], WhileStatement: [ - "test", - "body" + "body", + "test" ], WithStatement: [ - "object", - "body" + "body", + "object" ], YieldExpression: [ "argument" diff --git a/package.json b/package.json index 10ff44a..184ad43 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Constants and utilities about visitor keys to traverse AST.", "type": "module", "main": "dist/eslint-visitor-keys.cjs", + "types": "./dist/index.d.ts", "exports": { ".": [ { @@ -15,6 +16,8 @@ "./package.json": "./package.json" }, "files": [ + "dist/index.d.ts", + "dist/visitor-keys.d.ts", "dist/eslint-visitor-keys.cjs", "lib" ], @@ -22,21 +25,33 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "devDependencies": { + "@types/estree": "^0.0.51", + "@types/estree-jsx": "^0.0.1", + "@typescript-eslint/parser": "^5.11.0", "c8": "^7.7.3", + "chai": "^4.3.6", + "deep-equal-in-any-order": "^1.1.15", "eslint": "^7.29.0", "eslint-config-eslint": "^7.0.0", "eslint-plugin-jsdoc": "^35.4.0", "eslint-plugin-node": "^11.1.0", "eslint-release": "^3.2.0", + "esquery": "^1.4.0", + "json-diff": "^0.7.1", "mocha": "^9.0.1", "opener": "^1.5.2", - "rollup": "^2.52.1" + "rollup": "^2.52.1", + "tsd": "^0.19.1", + "typescript": "^4.5.5" }, "scripts": { "prepare": "npm run build", - "build": "rollup -c", + "build": "rollup -c && npm run tsc", "lint": "eslint .", - "test": "mocha tests/lib/**/*.cjs && c8 mocha tests/lib/**/*.js", + "tsc": "tsc", + "tsd": "tsd", + "build-keys": "node tools/build-keys-from-ts", + "test": "mocha tests/lib/**/*.cjs && c8 mocha tests/lib/**/*.js && npm run tsd", "coverage": "c8 report --reporter lcov && opener coverage/lcov-report/index.html", "generate-release": "eslint-generate-release", "generate-alpharelease": "eslint-generate-prerelease alpha", diff --git a/test-d/index.test-d.ts b/test-d/index.test-d.ts new file mode 100644 index 0000000..8ea4d8e --- /dev/null +++ b/test-d/index.test-d.ts @@ -0,0 +1,69 @@ +import { expectType, expectAssignable, expectError } from 'tsd'; + +import { KEYS, getKeys, unionWith, KeysStrict, KeysStrictReadonly } from "../lib/index.js"; + +const assignmentExpression = { + type: "AssignmentExpression", + operator: "=", + left: { + type: "Identifier", + name: "a", + range: [ + 0, + 1 + ] + }, + right: { + type: "Literal", + value: 5, + raw: "5", + range: [ + 4, + 5 + ] + }, + range: [ + 0, + 5 + ] +}; + +expectType<{readonly [type: string]: readonly string[]}>(KEYS); + +expectType(getKeys(assignmentExpression)); + +expectType<{readonly [type: string]: readonly string[]}>(unionWith({ + TestInterface1: ["left", "right"], + TestInterface2: ["expression"] +})); + +const keys: { + [type: string]: readonly string[] +} = { + TestInterface1: ["left", "right"] +}; + +const readonlyKeys: { + readonly [type: string]: readonly string[] +} = { + TestInterface1: ["left", "right"] +}; + +expectAssignable(keys); + +expectAssignable(readonlyKeys); + +expectError(() => { + const erring: KeysStrict = { + TestInterface1: ["left", "right"] + }; + erring.TestInterface1 = "badType"; +}); + +// https://github.com/SamVerschueren/tsd/issues/143 +// expectError(() => { +// const erring: KeysStrictReadonly = { +// TestInterface1: ["left", "right"] +// }; +// erring.TestInterface1 = ["badAttemptOverwrite"]; +// }); diff --git a/tests/lib/get-keys-from-ts.js b/tests/lib/get-keys-from-ts.js new file mode 100644 index 0000000..432919a --- /dev/null +++ b/tests/lib/get-keys-from-ts.js @@ -0,0 +1,43 @@ +import { diffString } from "json-diff"; +import chai, { expect } from "chai"; +import deepEqualInAnyOrder from "deep-equal-in-any-order"; +import { alphabetizeKeyInterfaces, getKeysFromTsFile } from "../../tools/get-keys-from-ts.js"; +import { KEYS } from "../../lib/index.js"; + +chai.use(deepEqualInAnyOrder); + +describe("getKeysFromTsFile", () => { + it("gets keys", async () => { + const { keys, tsInterfaceDeclarations } = await getKeysFromTsFile( + "./node_modules/@types/estree/index.d.ts" + ); + const { keys: jsxKeys } = await getKeysFromTsFile( + "./node_modules/@types/estree-jsx/index.d.ts", + { + supplementaryDeclarations: tsInterfaceDeclarations + } + ); + + /** + * Alphabetizes the keys of the interface-keys object + * @param {KeysStrict} keysObj The interface-keys object to sort + * @returns {KeysStrict} The alphabetized keys + */ + function alphabetizeKeys(keysObj) { + const retObj = {}; + + for (const [interfaceName, keysArr] of Object.entries(keysObj)) { + retObj[interfaceName] = [...keysArr].sort(); + } + return retObj; + } + + const actual = alphabetizeKeys(alphabetizeKeyInterfaces({ ...keys, ...jsxKeys })); + + const expected = alphabetizeKeys(KEYS); + + console.log(diffString(actual, expected)); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/tools/build-keys-from-ts.js b/tools/build-keys-from-ts.js new file mode 100644 index 0000000..aef13b7 --- /dev/null +++ b/tools/build-keys-from-ts.js @@ -0,0 +1,45 @@ +import fs from "fs"; +import { alphabetizeKeyInterfaces, getKeysFromTsFile } from "./get-keys-from-ts.js"; + +const { promises: { writeFile } } = fs; + +(async () => { + const { keys, tsInterfaceDeclarations } = await getKeysFromTsFile("./node_modules/@types/estree/index.d.ts"); + const { keys: jsxKeys } = await getKeysFromTsFile( + "./node_modules/@types/estree-jsx/index.d.ts", + { + supplementaryDeclarations: tsInterfaceDeclarations + } + ); + + const mergedKeys = alphabetizeKeyInterfaces({ ...keys, ...jsxKeys }); + + // eslint-disable-next-line no-console -- CLI + console.log("keys", mergedKeys); + + writeFile( + "./lib/visitor-keys.js", + // eslint-disable-next-line indent -- Readability +`/** + * @typedef {import('./index.js').KeysStrictReadonly} KeysStrictReadonly + */ + +/** + * @type {KeysStrictReadonly} + */ +const KEYS = ${JSON.stringify(mergedKeys, null, 4).replace(/"(.*?)":/gu, "$1:")}; + +// Types. +const NODE_TYPES = Object.keys(KEYS); + +// Freeze the keys. +for (const type of NODE_TYPES) { + Object.freeze(KEYS[type]); +} +Object.freeze(KEYS); + +export default KEYS; +` + ); + +})(); diff --git a/tools/get-keys-from-ts.js b/tools/get-keys-from-ts.js new file mode 100644 index 0000000..fad3e41 --- /dev/null +++ b/tools/get-keys-from-ts.js @@ -0,0 +1,370 @@ +import { promises } from "fs"; + +import { parseForESLint } from "@typescript-eslint/parser"; + +import esquery from "esquery"; + +import { getKeys } from "../lib/index.js"; + +const { readFile } = promises; + +const propertiesToIgnore = new Set([ + "comments", + "innerComments", + "type", + "operator" +]); + +const exemptedTypes = new Set([ + "TSBooleanKeyword", + "TSNumberKeyword", + "TSStringKeyword", + "TSLiteralType", // E.g., `true` + + // Apparently used for primitives, so exempting + "TSTypeLiteral", // E.g., `{value: {cooked, raw}}` + + "TSUnionType", // I.e., `|` + "TSTypeReference" +]); + +// Also excluding interfaces starting with "Base" in some contexts +const interfacesToIgnore = new Set([ + "Comment", + "Position", + "RegExp", + "SourceLocation" +]); + +/** + * Whether to ignore interface + * @param {string} type Type to check. + * @returns {boolean} If to be ignored. + */ +function ignoreInterface(type) { + return type.startsWith("Base") || interfacesToIgnore.has(type); +} + +// All items ending in `Statement` are also traversable +const traversableTypes = new Set([ + "Array", + "CatchClause", + "ChainElement", + "ClassBody", + "Declaration", + "Expression", + "FunctionExpression", + "Identifier", + "JSXClosingFragment", + "JSXIdentifier", + "JSXMemberExpression", + "JSXOpeningElement", + "JSXOpeningFragment", + "JSXClosingElement", + "Literal", + "Pattern", + "TemplateLiteral", + "VariableDeclaration" +]); + +const notTraversableTypes = new Set([ + "TSUndefinedKeyword", + "TSNullKeyword", + "TSBooleanKeyword", + "TSNumberKeyword", + "TSStringKeyword", + "TSBigIntKeyword", + "TSLiteralType" +]); + +/** + * Checks if a name is traverseable + * @param {string} name The name to check + * @returns {boolean} Whether it is traversable. + */ +function isTraversable(name) { + return name && (name.endsWith("Statement") || traversableTypes.has(name)); +} + +/** + * Determine whether the Node is traversable + * @param {Node} annotationType The annotation type Node + * @returns {boolean} Whether the node is traversable + */ +function checkTraversability(annotationType) { + if ( + notTraversableTypes.has(annotationType.type) || + interfacesToIgnore.has(annotationType.typeName?.name) + ) { + return false; + } + + if (annotationType.type === "TSTupleType") { + return annotationType.elementTypes.some(annType => checkTraversability(annType)); + } + + if (!isTraversable(annotationType.typeName.name)) { + + // Todo? + /* + const innerInterfaceName = tsAnnotation.typeName.name; + const innerTsDeclarationNode = findTsInterfaceDeclaration(innerInterfaceName); + + if (!innerTsDeclarationNode) { + + const innerTsTypeNode = findTsTypeDeclaration(innerInterfaceName); + + // We might iterate types here to see if children are iterable and + // fail if not + + unrecognizedTSTypeReferences.add(`${tsAnnotation.typeName.name}`); + break; + } + + // We might iterate interfaces here to see if children are iterable + // (see `addNodeForInterface` for a pattern of iteration) + */ + + throw new Error(`Type unknown as to traversability: ${annotationType.typeName.name}`); + } + + return true; +} + +/** + * Get the literal names out of AST + * @param {Node} excludedItem Excluded node + * @returns {string[]} The literal names + */ +function findOmitTypes(excludedItem) { + if (excludedItem.type === "TSUnionType") { + return excludedItem.types.map(typeNode => findOmitTypes(typeNode)); + } + if (excludedItem.type !== "TSLiteralType") { + throw new Error("Processing of non-literals in `Omit` not currently supported"); + } + return excludedItem.literal.value; +} + +/** + * Checks whether property should be excluded + * @param {string} property Property to check + * @param {string[]} excludedProperties Properties not to allow + * @returns {boolean} Whether or not to be excluded + */ +function isPropertyExcluded(property, excludedProperties) { + return propertiesToIgnore.has(property) || + (excludedProperties && excludedProperties.includes(property)); +} + +/** + * Returns alphabetized keys + * @param {KeysStrict} initialNodes Initial node list to sort + * @returns {KeysStrict} The keys + */ +function alphabetizeKeyInterfaces(initialNodes) { + + /** + * Alphabetize + * @param {string} typeA The first type to compare + * @param {string} typeB The second type to compare + * @returns {1|-1} The sorting index + */ + function alphabetize([typeA], [typeB]) { + return typeA < typeB ? -1 : 1; + } + const sortedNodeEntries = Object.entries(initialNodes).sort(alphabetize); + + for (const [, keys] of sortedNodeEntries) { + keys.sort(alphabetize); + } + + console.log('sortedNodeEntries', sortedNodeEntries); + + return Object.fromEntries(sortedNodeEntries); +} + +/** + * Builds visitor keys based on TypeScript declaration. + * @param {string} code TypeScript declaration file as code to parse. + * @param {{supplementaryDeclarations: Node[]}} [options] The options + * @returns {Promise<{tsInterfaceDeclarations: Node[], keys: KeysStrict}>} The built visitor keys + */ +async function getKeysFromTs(code, { + + // Todo: Ideally we'd just get these from the import + supplementaryDeclarations = [] +} = {}) { + const unrecognizedTSTypeReferences = new Set(); + const unrecognizedTSTypes = new Set(); + + const parsedTSDeclaration = parseForESLint(code); + const tsInterfaceDeclarations = [...esquery.query( + parsedTSDeclaration.ast, + "TSInterfaceDeclaration", + { + + // TypeScript keys here to find our *.d.ts nodes (not for the ESTree + // ones we want) + visitorKeys: parsedTSDeclaration.visitorKeys + } + ), ...supplementaryDeclarations]; + + // const tsTypeDeclarations = esquery.query( + // parsedTSDeclaration.ast, + // "TSTypeAliasDeclaration", + // { + // + // // TypeScript keys here to find our *.d.ts nodes (not for the ESTree + // // ones we want) + // visitorKeys: parsedTSDeclaration.visitorKeys + // } + // ); + const initialNodes = {}; + + /** + * Finds a TypeScript interfaction declaration. + * @param {string} interfaceName The interface name. + * @returns {Node} The interface declaration node + */ + function findTsInterfaceDeclaration(interfaceName) { + return tsInterfaceDeclarations.find( + innerTsDeclaration => innerTsDeclaration.id.name === interfaceName + ); + } + + /** + * Adds a node for a given interface. + * @param {string} interfaceName Name of the interface + * @param {Node} tsDeclarationNode TypeScript declaration node + * @param {Node} node The Node on which to build + * @param {string[]} excludedProperties Excluded properties + * @returns {void} + */ + function addNodeForInterface(interfaceName, tsDeclarationNode, node, excludedProperties) { + if (interfacesToIgnore.has(interfaceName)) { + return; + } + + const tsPropertySignatures = tsDeclarationNode.body.body; + + for (const tsPropertySignature of tsPropertySignatures) { + const property = tsPropertySignature.key.name; + + if (isPropertyExcluded(property, excludedProperties)) { + continue; + } + + const tsAnnotation = tsPropertySignature.typeAnnotation.typeAnnotation; + const tsPropertyType = tsAnnotation.type; + + // For sanity-checking + if (!exemptedTypes.has(tsPropertyType)) { + unrecognizedTSTypes.add(tsPropertyType); + continue; + } + + switch (tsPropertyType) { + case "TSUnionType": + if (tsAnnotation.types.some(checkTraversability)) { + break; + } + continue; + case "TSTypeReference": { + if (checkTraversability(tsAnnotation)) { + break; + } + + continue; + } default: + continue; + } + + node[property] = null; + } + + for (const extension of tsDeclarationNode.extends || []) { + const { typeParameters, expression } = extension; + const innerInterfaceName = expression.name; + + if (typeParameters) { + if (innerInterfaceName !== "Omit") { + throw new Error("Unknown extension type with parameters"); + } + + const [param, ...excludedAST] = typeParameters.params; + const paramInterfaceName = param.typeName.name; + const excluded = excludedAST.flatMap(findOmitTypes); + const innerTsDeclarationNode = findTsInterfaceDeclaration(paramInterfaceName); + + if (!innerTsDeclarationNode) { + unrecognizedTSTypeReferences.add(`${paramInterfaceName}`); + return; + } + + addNodeForInterface(paramInterfaceName, innerTsDeclarationNode, node, excluded); + } else { + const innerTsDeclarationNode = findTsInterfaceDeclaration(innerInterfaceName); + + if (!innerTsDeclarationNode) { + unrecognizedTSTypeReferences.add(`${innerInterfaceName}`); + return; + } + + addNodeForInterface(innerInterfaceName, innerTsDeclarationNode, node); + } + } + } + + for (const tsDeclarationNode of tsInterfaceDeclarations) { + const interfaceName = tsDeclarationNode.id.name; + + if (ignoreInterface(interfaceName)) { + continue; + } + + const node = {}; + + addNodeForInterface(interfaceName, tsDeclarationNode, node); + + initialNodes[interfaceName] = getKeys(node); + } + + const nodes = alphabetizeKeyInterfaces(initialNodes); + + if (unrecognizedTSTypes.size) { + throw new Error( + "Unhandled TypeScript type; please update the code to " + + "handle the type or if not relevant, add it to " + + "`unrecognizedTSTypes`; see\n\n " + + `${[...unrecognizedTSTypes].join(", ")}\n` + ); + } + if (unrecognizedTSTypeReferences.size) { + throw new Error( + "Unhandled TypeScript type reference; please update the code to " + + "handle the type reference or if not relevant, add it to " + + "`unrecognizedTSTypeReferences`; see\n\n " + + `${[...unrecognizedTSTypeReferences].join(", ")}\n` + ); + } + + return { + keys: nodes, + tsInterfaceDeclarations + }; +} + +/** + * Builds visitor keys based on TypeScript declaration. + * @param {string} file TypeScript declaration file to parse. + * @param {{supplementaryDeclarations: Node[]}} options The options + * @returns {Promise<{tsInterfaceDeclarations: Node[], keys: KeysStrict}} The built visitor keys + */ +async function getKeysFromTsFile(file, options) { + const code = await readFile(file); + + return await getKeysFromTs(code, options); +} + +export { alphabetizeKeyInterfaces, getKeysFromTs, getKeysFromTsFile }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5afc060 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "lib": ["es2020"], + "moduleResolution": "node", + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "strict": true, + "target": "es6", + "outDir": "dist" + }, + "include": ["lib/**/*.js"], + "exclude": ["node_modules"] +}