diff --git a/.babelrc b/.babelrc index 29a1161..fa3808b 100644 --- a/.babelrc +++ b/.babelrc @@ -1,13 +1,16 @@ { "presets": [ - [ "@babel/env", { - "modules": false - } ], - "@babel/preset-typescript" + "@babel/preset-typescript", + [ + "@babel/env", + { + "modules": false + } + ] ], "env": { "test": { - "presets": [ "@babel/env" ] + "presets": ["@babel/preset-typescript", "@babel/env"] } } } diff --git a/.eslintrc.json b/.eslintrc.json index f9da10c..ebd81cd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,20 +1,12 @@ { - "root": true, - "extends": ["eslint:recommended", "prettier", "plugin:@typescript-eslint/recommended"], - "plugins": ["prettier"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module", - "project": ["./tsconfig.json", "./tsconfig.eslint.json"] - }, + "extends": "@aduth/eslint-config", "env": { - "es6": true, "browser": true, "mocha": true, "node": true }, "rules": { - "prettier/prettier": "error" + "no-redeclare": "off", + "@typescript-eslint/no-redeclare": "error" } } diff --git a/.gitignore b/.gitignore index 9d27333..3d5ae6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /es/* -!/es/index.d.ts node_modules/ *.log dist/ diff --git a/mocha-setup.js b/mocha-setup.js new file mode 100644 index 0000000..f437bb5 --- /dev/null +++ b/mocha-setup.js @@ -0,0 +1,3 @@ +import register from '@babel/register'; + +register({ extensions: ['.ts'] }); diff --git a/package-lock.json b/package-lock.json index a246c19..3cf2fa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.3.0", "license": "MIT", "devDependencies": { + "@aduth/eslint-config": "^4.4.1", "@babel/cli": "^7.20.7", "@babel/core": "^7.20.12", "@babel/preset-env": "^7.20.2", @@ -17,19 +18,67 @@ "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.4.0", - "@typescript-eslint/eslint-plugin": "^5.53.0", - "@typescript-eslint/parser": "^5.53.0", + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^5.55.0", + "@typescript-eslint/parser": "^5.55.0", "chai": "^4.3.7", "eslint": "^8.34.0", - "eslint-config-prettier": "^8.6.0", + "eslint-config-prettier": "^8.7.0", "eslint-plugin-prettier": "^4.2.1", "jsdom": "^21.1.0", + "jsdom-global": "^3.0.2", "mocha": "^10.2.0", "prettier": "^2.8.4", "rollup": "^3.15.0", "typescript": "^5.0.2" } }, + "node_modules/@aduth/eslint-config": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@aduth/eslint-config/-/eslint-config-4.4.1.tgz", + "integrity": "sha512-0DzJLhVxFtdBv8ONS1jgz/gbKDHBzv+piev07pSi5sgnMepxBGbFgRZeASVGyWo9njIQE6ExnCIvemittg44Lw==", + "dev": true, + "dependencies": { + "@aduth/is-dependency": "^1.0.0", + "deepmerge": "^4.2.2" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": ">=4.27.0", + "@typescript-eslint/parser": ">=4.27.0", + "eslint": ">=7.28.0", + "eslint-config-prettier": ">=8.3.0", + "eslint-plugin-jsdoc": ">=35.3.0", + "eslint-plugin-prettier": ">=3.4.0", + "typescript": ">=4.3.4" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + }, + "eslint-plugin-jsdoc": { + "optional": true + }, + "eslint-plugin-prettier": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@aduth/is-dependency": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@aduth/is-dependency/-/is-dependency-1.0.0.tgz", + "integrity": "sha512-1CJwTd6B6XDb6HETcHMwjRmQLQi2FgjM1rYUO6wf1yizKVPC9V66vmvVCtgePMzWmhsLqdhOPLWt+fNvG46FoA==", + "dev": true + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1777,6 +1826,30 @@ "node": ">=6.9.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", + "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -2065,6 +2138,12 @@ "node": ">= 10" } }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", @@ -2077,6 +2156,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -2090,19 +2175,19 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", - "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz", + "integrity": "sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/type-utils": "5.54.1", - "@typescript-eslint/utils": "5.54.1", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.55.0", + "@typescript-eslint/type-utils": "5.55.0", + "@typescript-eslint/utils": "5.55.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" }, @@ -2157,14 +2242,14 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", - "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.55.0.tgz", + "integrity": "sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/scope-manager": "5.55.0", + "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/typescript-estree": "5.55.0", "debug": "^4.3.4" }, "engines": { @@ -2184,13 +2269,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", - "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", + "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/visitor-keys": "5.54.1" + "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/visitor-keys": "5.55.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2201,13 +2286,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", - "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz", + "integrity": "sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.54.1", - "@typescript-eslint/utils": "5.54.1", + "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/utils": "5.55.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -2228,9 +2313,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", - "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", + "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2241,13 +2326,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", - "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", + "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/visitor-keys": "5.54.1", + "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/visitor-keys": "5.55.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2301,18 +2386,18 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", - "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.55.0.tgz", + "integrity": "sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/scope-manager": "5.55.0", + "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/typescript-estree": "5.55.0", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", "semver": "^7.3.7" }, "engines": { @@ -2382,12 +2467,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", - "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", + "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/types": "5.55.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3166,9 +3251,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", - "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz", + "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -4214,6 +4299,15 @@ } } }, + "node_modules/jsdom-global": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz", + "integrity": "sha512-t1KMcBkz/pT5JrvcJbpUR2u/w1kO9jXctaaGJ0vZDzwFnIvGWw9IDSRciT83kIs8Bnw4qpOl8bQK08V01YgMPg==", + "dev": true, + "peerDependencies": { + "jsdom": ">=10.0.0" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", diff --git a/package.json b/package.json index b04b804..6d64aeb 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,15 @@ "src" ], "scripts": { - "build:es": "babel src/ --extensions \".js,.ts,\" --out-dir es", + "build:es": "babel src/ --extensions '.ts' --out-dir es", "build:umd": "rollup -c", - "build:types": "tsc -b", - "build": "npm run build:es && npm run build:umd", + "build:types": "tsc -b tsconfig.decl.json", + "build": "npm run build:es && npm run build:umd && npm run build:types", "dev": "rollup -c -w", "lint": "eslint . --ignore-pattern dist --ignore-pattern es", - "unit-test": "NODE_ENV=test mocha --require @babel/register", - "test": "npm run unit-test && npm run lint", + "unit-test": "NODE_ENV=test mocha -r jsdom-global/register -r @babel/register -r ./mocha-setup.js --extension ts", + "typecheck": "tsc", + "test": "npm run unit-test && npm run lint && npm run typecheck", "prepublishOnly": "npm run build" }, "author": { @@ -36,6 +37,7 @@ }, "license": "MIT", "devDependencies": { + "@aduth/eslint-config": "^4.4.1", "@babel/cli": "^7.20.7", "@babel/core": "^7.20.12", "@babel/preset-env": "^7.20.2", @@ -44,13 +46,16 @@ "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.4.0", - "@typescript-eslint/eslint-plugin": "^5.53.0", - "@typescript-eslint/parser": "^5.53.0", + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^5.55.0", + "@typescript-eslint/parser": "^5.55.0", "chai": "^4.3.7", "eslint": "^8.34.0", - "eslint-config-prettier": "^8.6.0", + "eslint-config-prettier": "^8.7.0", "eslint-plugin-prettier": "^4.2.1", "jsdom": "^21.1.0", + "jsdom-global": "^3.0.2", "mocha": "^10.2.0", "prettier": "^2.8.4", "rollup": "^3.15.0", diff --git a/rollup.config.mjs b/rollup.config.mjs index 6efbef9..27f14fa 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -12,10 +12,10 @@ export default [ }, plugins: [ nodeResolve({ - extensions: ['.js', '.ts'], + extensions: ['.ts'], }), babel({ - extensions: ['.js', '.ts'], + extensions: ['.ts'], babelHelpers: 'bundled', exclude: 'node_modules/**', presets: ['@babel/preset-typescript'], @@ -31,10 +31,10 @@ export default [ }, plugins: [ nodeResolve({ - extensions: ['.js', '.ts'], + extensions: ['.ts'], }), babel({ - extensions: ['.js', '.ts'], + extensions: ['.ts'], babelHelpers: 'bundled', exclude: 'node_modules/**', presets: ['@babel/preset-typescript'], diff --git a/src/index.ts b/src/index.ts index 87a0d9a..cc443fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,17 @@ */ import getPath from './get-path'; -type MatcherFn = (node: Element) => T | undefined; -type MatcherObj = { [key: string]: MatcherFn | MatcherObj }; -type Matcher = MatcherFn | MatcherObj; +export type MatcherFn = (node: Element) => T | undefined; + +export type MatcherObj = { [key: string]: MatcherFn | MatcherObj }; + +export type MatcherObjResult = { + [K in keyof O]: O[K] extends F + ? ReturnType + : O[K] extends MatcherObj + ? MatcherObjResult + : never; +}; /** * Function returning a DOM document created by `createHTMLDocument`. The same @@ -28,21 +36,56 @@ const getDocument = (() => { * Given a markup string or DOM element, creates an object aligning with the * shape of the matchers object, or the value returned by the matcher. * - * @param source Source content + * @param source Source content * @param matchers Matcher function or object of matchers - * @return Matched value(s), shaped by object */ -export function parse, O extends MatcherObj = {}>( - source: Element | string, +export function parse(source: string | Element, matchers?: undefined): undefined; + +/** + * Given a markup string or DOM element, creates an object aligning with the + * shape of the matchers object, or the value returned by the matcher. + * + * @param source Source content + * @param matchers Object of matchers + * @return Matched values, shaped by object + */ +export function parse( + source: string | Element, matchers: O -): { [K in keyof O]: O[K] extends F ? ReturnType : O[K] }; -export function parse>( - source: Element | string, - matchers: F -): ReturnType; -export function parse, O extends MatcherObj = {}>( - source: Element | string, +): MatcherObjResult; + +/** + * Given a markup string or DOM element, creates an object aligning with the + * shape of the matchers object, or the value returned by the matcher. + * + * @param source Source content + * @param matcher Matcher function + * @return Matched value + */ +export function parse(source: string | Element, matchers: F): ReturnType; + +/** + * Given a markup string or DOM element, creates an object aligning with the + * shape of the matchers object, or the value returned by the matcher. + * + * @param source Source content + * @param matchers Matcher function or object of matchers + */ +export function parse( + source: string | Element, matchers: O | F +): MatcherObjResult | ReturnType; + +/** + * Given a markup string or DOM element, creates an object aligning with the + * shape of the matchers object, or the value returned by the matcher. + * + * @param source Source content + * @param matchers Matcher function or object of matchers + */ +export function parse( + source: string | Element, + matchers?: O | F ) { if (!matchers) { return; @@ -56,7 +99,7 @@ export function parse, O extends MatcherObj = {}>( } // Return singular value - if ('function' === typeof matchers) { + if (typeof matchers === 'function') { return matchers(source); } @@ -66,24 +109,50 @@ export function parse, O extends MatcherObj = {}>( } // Shape result by matcher object - return Object.keys(matchers).reduce((memo, key) => { - memo[key] = parse(source, matchers[key] as MatcherObj); + return Object.keys(matchers).reduce((memo, key: keyof MatcherObjResult) => { + const inner = matchers[key]; + memo[key] = parse(source, inner); return memo; - }, {} as Record); + }, {} as MatcherObjResult); } +/** + * Generates a function which matches node of type selector, returning an + * attribute by property if the attribute exists. If no selector is passed, + * returns property of the query element. + * + * @param name Property name + * @return Property value + */ +export function prop(name: string): MatcherFn; + +/** + * Generates a function which matches node of type selector, returning an + * attribute by property if the attribute exists. If no selector is passed, + * returns property of the query element. + * + * @param selector Optional selector + * @param name Property name + * @return Property value + */ +export function prop( + selector: string | undefined, + name: N +): MatcherFn; + /** * Generates a function which matches node of type selector, returning an * attribute by property if the attribute exists. If no selector is passed, * returns property of the query element. * * @param selector Optional selector - * @param name Property name - * @return Property value + * @param name Property name + * @return Property value */ -export function prop(name: string): MatcherFn; -export function prop(selector: string | undefined, name: string): MatcherFn; -export function prop(arg1: string | undefined, arg2?: string): MatcherFn { +export function prop( + arg1: string | undefined, + arg2?: string +): MatcherFn { let name: string; let selector: string | undefined; if (1 === arguments.length) { @@ -93,29 +162,48 @@ export function prop(arg1: string | undefined, arg2?: string): MatcherF name = arg2 as string; selector = arg1; } - return function (node: Element) { + return function (node: Element): Element[N] | undefined { let match: Element | null = node; if (selector) { match = node.querySelector(selector); } if (match) { - return getPath(match, name) as T; + return getPath(match, name) as Element[N] | undefined; } - }; + } as MatcherFn; } +/** + * Generates a function which matches node of type selector, returning an + * attribute by name if the attribute exists. If no selector is passed, + * returns attribute of the query element. + * + * @param name Attribute name + * @return Attribute value + */ +export function attr(name: string): MatcherFn; + /** * Generates a function which matches node of type selector, returning an * attribute by name if the attribute exists. If no selector is passed, * returns attribute of the query element. * * @param selector Optional selector - * @param name Attribute name - * @return Attribute value + * @param name Attribute name + * @return Attribute value */ -export function attr(name: string): MatcherFn; -export function attr(selector: string | undefined, name: string): MatcherFn; -export function attr(arg1: string | undefined, arg2?: string): MatcherFn { +export function attr(selector: string | undefined, name: string): MatcherFn; + +/** + * Generates a function which matches node of type selector, returning an + * attribute by name if the attribute exists. If no selector is passed, + * returns attribute of the query element. + * + * @param selector Optional selector + * @param name Attribute name + * @return Attribute value + */ +export function attr(arg1: string | undefined, arg2?: string): MatcherFn { let name: string; let selector: string | undefined; if (1 === arguments.length) { @@ -125,10 +213,10 @@ export function attr(arg1: string | undefined, arg2?: string): MatcherF name = arg2 as string; selector = arg1; } - return function (node: Element): T | undefined { - const attributes = prop(selector, 'attributes')(node); + return function (node: Element): string | undefined { + const attributes = prop(selector, 'attributes')(node) as NamedNodeMap | undefined; if (attributes && Object.prototype.hasOwnProperty.call(attributes, name)) { - return attributes[name].value; + return attributes[name as any].value; } }; } @@ -139,10 +227,10 @@ export function attr(arg1: string | undefined, arg2?: string): MatcherF * @see prop() * * @param selector Optional selector - * @return Inner HTML + * @return Inner HTML */ -export function html(selector?: string) { - return prop(selector, 'innerHTML'); +export function html(selector?: string) { + return prop(selector, 'innerHTML') as MatcherFn; } /** @@ -150,11 +238,11 @@ export function html(selector?: string) { * * @see prop() * - * @param selector Optional selector - * @return Text content + * @param selector Optional selector + * @return Text content */ -export function text(selector?: string) { - return prop(selector, 'textContent'); +export function text(selector?: string) { + return prop(selector, 'textContent') as MatcherFn; } /** @@ -166,11 +254,14 @@ export function text(selector?: string) { * * @param selector Selector to match * @param matchers Matcher function or object of matchers - * @return Matcher function which returns an array of matched value(s) + * @return Matcher function which returns an array of matched value(s) */ -export function query(selector: string, matchers: Matcher) { - return function (node: Element): any[] { +export function query( + selector: string, + matchers?: F | O +) { + return function (node: Element) { const matches = node.querySelectorAll(selector); - return [].map.call(matches, (match) => parse>(match, matchers)); + return [].map.call(matches, (match) => parse(match, matchers!)) as MatcherObjResult[]; }; } diff --git a/test/get-path.js b/test/get-path.ts similarity index 100% rename from test/get-path.js rename to test/get-path.ts diff --git a/test/index.js b/test/index.ts similarity index 89% rename from test/index.js rename to test/index.ts index 17d585a..cefeb7d 100644 --- a/test/index.js +++ b/test/index.ts @@ -14,23 +14,12 @@ describe('hpq', () => { '

Andrew
'; // Element - let element; + let element: HTMLElement; before(() => { - const { JSDOM } = require('jsdom'); - global.window = new JSDOM('').window; - global.document = window.document; - global.navigator = window.navigator; - element = document.createElement('div'); element.innerHTML = markup; - element = element.firstChild; - }); - - after(() => { - delete global.document; - delete global.window; - delete global.navigator; + element = element.firstChild as HTMLElement; }); describe('parse()', () => { @@ -52,7 +41,8 @@ describe('hpq', () => { expect(result).to.equal('— Andrew'); }); - it('should return undefiend if passed matchers other than object, function', () => { + it('should return undefined if passed matchers other than object, function', () => { + // @ts-ignore const result = parse(element, 2); expect(result).to.be.undefined; @@ -71,6 +61,7 @@ describe('hpq', () => { describe('prop()', () => { it('should return a matcher function', () => { + // @ts-ignore const matcher = prop(); expect(matcher).to.be.a('function'); @@ -103,6 +94,7 @@ describe('hpq', () => { describe('attr()', () => { it('should return a matcher function', () => { + // @ts-ignore const matcher = attr(); expect(matcher).to.be.a('function'); @@ -180,12 +172,6 @@ describe('hpq', () => { }); describe('query()', () => { - it('should return a matcher function', () => { - const matcher = query(); - - expect(matcher).to.be.a('function'); - }); - it('should return array of parse on matched nodes', () => { const result = parse(element, { text: query('p', text()) }); diff --git a/tsconfig.decl.json b/tsconfig.decl.json new file mode 100644 index 0000000..7e6747b --- /dev/null +++ b/tsconfig.decl.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./es", + "rootDir": "src", + "noEmit": false + }, + "include": ["src"] +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json deleted file mode 100644 index ff08772..0000000 --- a/tsconfig.eslint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig", - "compilerOptions": { - "noEmit": true - }, - "include": ["**/*.js"] -} diff --git a/tsconfig.json b/tsconfig.json index bf631e0..e9c4b06 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,10 @@ { - "compilerOptions": { - "target": "ES2017", - "lib": ["ES2017", "DOM", "DOM.Iterable"], - "outDir": "./es", - "strict": true, - "allowJs": true, - "checkJs": true, - "declaration": true, - "emitDeclarationOnly": true - }, - "include": ["src/**/*.js"] + "compilerOptions": { + "target": "ES2017", + "lib": ["ES2017", "DOM", "DOM.Iterable"], + "moduleResolution": "node", + "strict": true, + "noEmit": true + }, + "include": ["src", "test"] }