From 6482651dd285f6fe45604a3465c3259b986c4576 Mon Sep 17 00:00:00 2001 From: Misha Moroshko Date: Sat, 25 Jul 2020 21:55:06 +1000 Subject: [PATCH] Add TypeScript support and sortObjectKeysByValue to processReport --- README.md | 20 +++----- package-lock.json | 113 ++++++++++++++++++++++++++++------------------ package.json | 5 +- src/run.js | 29 +++++++----- src/scan.js | 4 +- src/scan.test.js | 94 ++++++++++++++++++++++++++++++++++++++ src/utils.js | 17 +++++++ src/utils.test.js | 60 +++++++++++++++++++++++- 8 files changed, 267 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index e0f4e3a..3343b83 100644 --- a/README.md +++ b/README.md @@ -144,9 +144,9 @@ module.exports = { return ["utils", "tests"].includes(dir); }, - // [optional] defaults to: ["**/*.js"] + // [optional] defaults to: ["**/!(*.test|*.spec).@(js|ts)?(x)"] // Only files matching these globs will be scanned (see here for glob syntax: https://github.com/micromatch/picomatch#globbing-features). - globs: ["**/*.js", "**/*.jsx"], + globs: ["**/*.js"], // [optional] // Components to report on (omit to report on all components). @@ -171,10 +171,11 @@ module.exports = { // The processReport function gets an object with the following in it: // * report - the full JSON report // * forEachComponent - recursively visits every component in the report + // * sortObjectKeysByValue - sorts object keys by some function of the value (this function is identity by default) // * writeFile - use it to store the result object in a file // When processReport is not specified, the report is logged out. - processReport: ({ forEachComponent, writeFile }) => { - const output = {}; + processReport: ({ forEachComponent, sortObjectKeysByValue, writeFile }) => { + let output = {}; // count instances forEachComponent(({ componentName, component }) => { @@ -185,18 +186,11 @@ module.exports = { } }); - // sort by instances count - const entries = Object.entries(output); - - entries.sort(([name1, count1], [name2, count2]) => - count1 > count2 || (count1 === count2 && name1 <= name2) ? -1 : 1 - ); - - const result = Object.fromEntries(entries); + output = sortObjectKeysByValue(output); writeFile( "./reports/oscar.json", // absolute or relative to the config file location - result // must be an object, will be JSON.stringified + output // must be an object, will be JSON.stringified ); }, }; diff --git a/package-lock.json b/package-lock.json index cd6b292..88d88ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-scanner", - "version": "0.1.1", + "version": "0.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -130,6 +130,34 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@typescript-eslint/types": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.7.0.tgz", + "integrity": "sha512-reCaK+hyKkKF+itoylAnLzFeNYAEktB0XVfSQvf0gcVgpz1l49Lt6Vo9x4MVCCxiDydA0iLAjTF/ODH0pbfnpg==" + }, + "@typescript-eslint/typescript-estree": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.7.0.tgz", + "integrity": "sha512-xr5oobkYRebejlACGr1TJ0Z/r0a2/HUf0SXqPvlgUMwiMqOCu/J+/Dr9U3T0IxpE5oLFSkqMx1FE/dKaZ8KsOQ==", + "requires": { + "@typescript-eslint/types": "3.7.0", + "@typescript-eslint/visitor-keys": "3.7.0", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.7.0.tgz", + "integrity": "sha512-k5PiZdB4vklUpUX4NBncn5RBKty8G3ihTY+hqJsCdMuD0v4jofI5xuqwnVcWxfv6iTm2P/dfEa2wMUnsUY8ODw==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, "acorn": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", @@ -223,14 +251,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -320,8 +346,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "contains-path": { "version": "0.1.0", @@ -386,7 +411,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, "requires": { "ms": "^2.1.1" } @@ -545,14 +569,6 @@ "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - } } }, "eslint-import-resolver-node": { @@ -679,8 +695,7 @@ "eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" }, "espree": { "version": "7.2.0", @@ -829,8 +844,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", @@ -864,7 +878,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -986,7 +999,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -995,8 +1007,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-arrayish": { "version": "0.2.1", @@ -1019,8 +1030,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -1032,7 +1042,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -1195,8 +1204,7 @@ "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "make-dir": { "version": "3.1.0", @@ -1205,18 +1213,20 @@ "dev": true, "requires": { "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "meriyah": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-2.1.1.tgz", - "integrity": "sha512-Z9W+J5zeYY7lb9AQml90o6+HmP6qfs3PfM460eoD+EudNbM9wbnUQ/7Ko2S2SqR4EaaPIb6eI56qG2/KB7kByA==" - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1244,8 +1254,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "natural-compare": { "version": "1.4.0", @@ -1313,7 +1322,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -1389,8 +1397,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -1646,10 +1653,9 @@ "dev": true }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" }, "semver-compare": { "version": "1.0.0", @@ -1931,6 +1937,19 @@ "strip-bom": "^3.0.0" } }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "requires": { + "tslib": "^1.8.1" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1946,6 +1965,11 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -2045,8 +2069,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index a531cdf..6e31a98 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,14 @@ } }, "dependencies": { + "@typescript-eslint/typescript-estree": "3.7.0", "astray": "1.0.0", "dlv": "1.1.3", "dset": "2.0.1", "fdir": "3.4.3", - "meriyah": "2.1.1", "picomatch": "2.2.2", - "sade": "1.7.3" + "sade": "1.7.3", + "typescript": "3.9.7" }, "devDependencies": { "c8": "7.2.1", diff --git a/src/run.js b/src/run.js index 57136b9..cb04dca 100644 --- a/src/run.js +++ b/src/run.js @@ -2,10 +2,16 @@ const fs = require("fs"); const path = require("path"); const fdir = require("fdir"); const scan = require("./scan"); -const { pluralize, forEachComponent } = require("./utils"); +const { + pluralize, + forEachComponent, + sortObjectKeysByValue, +} = require("./utils"); + +const DEFAULT_GLOBS = ["**/!(*.test|*.spec).@(js|ts)?(x)"]; function run({ config, configDir, crawlFrom, startTime }) { - const globs = config.globs || ["**/*.js"]; + const globs = config.globs || DEFAULT_GLOBS; const files = new fdir() .glob(...globs) .exclude(config.exclude) @@ -29,19 +35,11 @@ function run({ config, configDir, crawlFrom, startTime }) { }); } - const endTime = process.hrtime.bigint(); - - // eslint-disable-next-line no-console - console.log( - `Scanned ${pluralize(files.length, "file")} in ${ - Number(endTime - startTime) / 1e9 - } seconds` - ); - if (typeof config.processReport === "function") { config.processReport({ report, forEachComponent: forEachComponent(report), + sortObjectKeysByValue, writeFile: (outputPath, object) => { const data = JSON.stringify(object, null, 2); const filePath = path.resolve(configDir, outputPath); @@ -57,6 +55,15 @@ function run({ config, configDir, crawlFrom, startTime }) { // eslint-disable-next-line no-console console.log(JSON.stringify(report, null, 2)); } + + const endTime = process.hrtime.bigint(); + + // eslint-disable-next-line no-console + console.log( + `Scanned ${pluralize(files.length, "file")} in ${ + Number(endTime - startTime) / 1e9 + } seconds` + ); } module.exports = run; diff --git a/src/scan.js b/src/scan.js index 4051266..919a726 100644 --- a/src/scan.js +++ b/src/scan.js @@ -1,11 +1,9 @@ -const { parse } = require("meriyah"); +const { parse } = require("@typescript-eslint/typescript-estree"); const astray = require("astray"); const getObjectPath = require("dlv"); const setObjectPath = require("dset"); const parseOptions = { - module: true, - next: true, loc: true, jsx: true, }; diff --git a/src/scan.test.js b/src/scan.test.js index 9fec2e9..2454ddf 100644 --- a/src/scan.test.js +++ b/src/scan.test.js @@ -12,6 +12,7 @@ Scan.before((context) => { code, filePath, components: { + Box: true, Header: true, Text: true, }, @@ -475,4 +476,97 @@ Scan("ignores non-JSX stuff", ({ getReport }) => { }); }); +Scan("typescript", ({ getReport }) => { + const report = getReport( + "typescript.ts", + ` + /* @jsx jsx */ + import { jsx } from '@emotion/core'; // eslint-disable-line + import React, { ReactNode, ElementType, useContext } from 'react'; // eslint-disable-line + import { Box, BoxProps, useTheme } from '@chakra-ui/core'; + import capsize from 'capsize'; + import siteFontContext from './SiteProvider'; + import { FontMetrics } from 'capsize'; + import fontSizes from '../fontSizes'; + + export interface HeadingProps { + children: ReactNode; + as?: ElementType; + size?: '1' | '2' | '3'; + align?: BoxProps['textAlign']; + } + + const element = { + '1': 'h1', + '2': 'h2', + '3': 'h3', + } as const; + + const color = { + '1': 'blue.900', + '2': 'blue.800', + '3': 'gray.500', + }; + const capsizeForSize = (size: number, font: FontMetrics) => + capsize({ + capHeight: size, + leading: Math.floor(size * 1.9), + fontMetrics: font, + }); + + const Heading = ({ children, as, size = '1', align }: HeadingProps) => { + const activeFont = useContext(siteFontContext); + const theme = useTheme(); + + const mq = (theme.breakpoints as string[]) + .slice(0, 4) + .map((bp) => \`@media (min-width: \${bp})\`); + + return ( + + {children} + + ); + }; + + export default Heading; + ` + ); + + assert.equal(report, { + Box: { + instances: [ + { + props: { + as: "(LogicalExpression)", + fontFamily: "(MemberExpression)", + color: "(MemberExpression)", + textAlign: "(Identifier)", + css: "(ObjectExpression)", + }, + propsSpread: false, + location: { + file: "typescript.ts", + start: { + line: 45, + column: 9, + }, + }, + }, + ], + }, + }); +}); + Scan.run(); diff --git a/src/utils.js b/src/utils.js index 2e22f74..da1e0ce 100644 --- a/src/utils.js +++ b/src/utils.js @@ -50,8 +50,25 @@ const forEachComponent = (report) => (callback) => { } }; +function sortObjectKeysByValue(obj, mapValue = (value) => value) { + const entries = Object.entries(obj); + + entries.sort(([key1, value1], [key2, value2]) => { + const value1ToCompare = mapValue(value1); + const value2ToCompare = mapValue(value2); + + return value1ToCompare > value2ToCompare || + (value1ToCompare === value2ToCompare && key1 <= key2) + ? -1 + : 1; + }); + + return Object.fromEntries(entries); +} + module.exports = { validateConfig, pluralize, forEachComponent, + sortObjectKeysByValue, }; diff --git a/src/utils.test.js b/src/utils.test.js index e2efcd8..35f86eb 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -2,11 +2,17 @@ const fs = require("fs"); const path = require("path"); const { suite } = require("uvu"); const assert = require("uvu/assert"); -const { validateConfig, pluralize, forEachComponent } = require("./utils"); +const { + validateConfig, + pluralize, + forEachComponent, + sortObjectKeysByValue, +} = require("./utils"); const ValidateConfig = suite("validateConfig"); const Pluralize = suite("pluralize"); const ForEachComponent = suite("forEachComponent"); +const SortObjectKeysByValue = suite("sortObjectKeysByValue"); ValidateConfig("crawlFrom is missing", () => { const originalPathResolve = path.resolve; @@ -165,6 +171,58 @@ ForEachComponent("visits every component", () => { ]); }); +SortObjectKeysByValue("default valueMap", () => { + const result = sortObjectKeysByValue({ + Header: 5, + Link: 10, + Accordion: 7, + Footer: 16, + }); + + assert.equal(result, { + Footer: 16, + Link: 10, + Accordion: 7, + Header: 5, + }); +}); + +SortObjectKeysByValue("custom valueMap", () => { + const result = sortObjectKeysByValue( + { + Header: { + instances: 16, + }, + Link: { + instances: 10, + }, + Accordion: { + instances: 7, + }, + Footer: { + instances: 16, + }, + }, + (component) => component.instances + ); + + assert.equal(result, { + Footer: { + instances: 16, + }, + Link: { + instances: 10, + }, + Accordion: { + instances: 7, + }, + Header: { + instances: 16, + }, + }); +}); + ValidateConfig.run(); Pluralize.run(); ForEachComponent.run(); +SortObjectKeysByValue.run();