Skip to content

Commit db098a2

Browse files
authored
feat(scripts): add depcheck script and API (/api/depcheck)
1 parent ba848a9 commit db098a2

File tree

13 files changed

+798
-35
lines changed

13 files changed

+798
-35
lines changed

.depcheckrc.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"specials": ["babel", "bin", "eslint", "jest", "lint-staged", "tslint"],
3+
"ignoreMatches": [
4+
"@types/*",
5+
"concurrently",
6+
"doctoc",
7+
"eslint-config-*",
8+
"eslint-plugin-*",
9+
"jest-github-actions-reporter",
10+
"lint-staged",
11+
"tslib"
12+
]
13+
}

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
- [Prettier](#prettier)
4040
- [Jest](#jest)
4141
- [Semantic Release](#semantic-release)
42+
- [Depcheck](#depcheck)
4243
- [Lint Staged](#lint-staged)
4344
- [Source Control Hooks](#source-control-hooks)
4445
- [Husky Example](#husky-example)
@@ -167,6 +168,31 @@ module.exports = {
167168
}
168169
```
169170

171+
#### Depcheck
172+
173+
> ℹ️ The `hoverBabel` special requires NODE_PATH to be defined to resolve the
174+
> babel config file
175+
176+
Or, for depcheck in `.depcheckrc.json'`:
177+
178+
```json
179+
{
180+
"specials": [
181+
"babel",
182+
"bin",
183+
"jest",
184+
[
185+
"hoverBabel",
186+
{
187+
"config": "babel.config.js",
188+
"env": "development"
189+
}
190+
]
191+
],
192+
"ignoreMatches": ["types/*"]
193+
}
194+
```
195+
170196
#### Lint Staged
171197

172198
Or, for lint-staged (used in `pre-commit` script) in `lint-staged.config.js`:

api/depcheck.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '../dist/api/depcheck'

api/depcheck.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../dist/api/depcheck')

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"build:types": "tsc -p src/",
2020
"ci-after-success": "node src ci-after-success",
2121
"commit": "node src commit",
22+
"depcheck": "node src depcheck",
2223
"format": "node src format",
2324
"lint": "node src lint",
2425
"prepublishOnly": "yarn build",
@@ -53,6 +54,11 @@
5354
"@commitlint/config-conventional": "^16.2.1",
5455
"@commitlint/prompt": "^16.1.0",
5556
"@types/jest": "^27.0.2",
57+
"@types/lodash.has": "^4.5.6",
58+
"@types/mkdirp": "^1.0.2",
59+
"@types/node": ">=17.x",
60+
"@types/rimraf": "^3.0.2",
61+
"@types/which": "^2.0.1",
5662
"@typescript-eslint/eslint-plugin": "^5.13.0",
5763
"@typescript-eslint/parser": "^5.13.0",
5864
"arrify": "^2.0.1",
@@ -63,7 +69,6 @@
6369
"cross-spawn": "^7.0.1",
6470
"doctoc": "^2.1.0",
6571
"eslint": "^8.8.0",
66-
"@types/node": ">=17.x",
6772
"eslint-config-airbnb": "19.0.0",
6873
"eslint-config-airbnb-typescript": "^16.1.0",
6974
"eslint-config-prettier": "^8.3.0",
@@ -126,6 +131,7 @@
126131
"@babel/preset-env": "^7.16.11",
127132
"@types/cross-spawn": "^6.0.2",
128133
"babel-jest": "^27.2.4",
134+
"depcheck": "^1.4.3",
129135
"eslint-config-kentcdodds": "^20.0.1",
130136
"husky": "^7.0.4",
131137
"jest-in-case": "^1.0.2",

src/__tests__/__snapshots__/index.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Available Scripts:
2424
ci-after-success
2525
commit-msg
2626
commit
27+
depcheck
2728
format
2829
lint
2930
pre-commit

src/api/depcheck/depcheck.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const fs = require('fs')
2+
const depcheck = require('depcheck')
3+
const hoverSpecials = require('./specials')
4+
const {hasFile, uniq} = require('../../utils')
5+
6+
/**
7+
* @typedef Config
8+
* @property {boolean} [ignoreBinPackage]
9+
* @property {boolean} [skipMissing]
10+
* @property {string[]} [ignorePatterns]
11+
* @property {string[]} [ignoreMatches]
12+
* @property {string[]} [parsers]
13+
* @property {string[]} [detectors]
14+
* @property {string[]} [specials]
15+
*/
16+
17+
/**
18+
* @param {string} configPath
19+
* @return {Config}
20+
*/
21+
const resolveConfig = configPath => {
22+
let loadedOptions = {}
23+
if (!!configPath && hasFile(configPath)) {
24+
loadedOptions = JSON.parse(fs.readFileSync(configPath, {encoding: 'utf8'}))
25+
}
26+
27+
return loadedOptions
28+
}
29+
30+
/**
31+
*
32+
* @param {{[key: string]: string}} parsers
33+
* @return {{[key: string]: depcheck.Parser}}
34+
*/
35+
const resolveParsers = parsers => {
36+
/** @var {{[key: string]: depcheck.Parser}} resolvedParsers */
37+
const resolvedParsers = {}
38+
Object.keys(parsers).forEach(key => {
39+
// @ts-ignore
40+
if (depcheck.parser[parsers[key]]) {
41+
// @ts-ignore
42+
resolvedParsers[key] = depcheck.parser[parsers[key]]
43+
}
44+
})
45+
46+
// @ts-ignore
47+
return resolvedParsers
48+
}
49+
50+
/**
51+
*
52+
* @param {string[]}detectors
53+
* @return {depcheck.Detector[]}
54+
*/
55+
const resolveDetectors = detectors => {
56+
return uniq(
57+
detectors.map(detector => {
58+
// @ts-ignore
59+
if (depcheck.detector[detector]) {
60+
//@ts-ignore
61+
return depcheck.detector[detector]
62+
}
63+
throw new Error(`Undefined detector ${detector}`)
64+
}),
65+
)
66+
}
67+
68+
/**
69+
*
70+
* @param {*[]} specials
71+
* @return {depcheck.Parser[]}
72+
*/
73+
const resolveSpecials = specials => {
74+
return uniq(
75+
specials.map(special => {
76+
if (typeof special === 'string') {
77+
// @ts-ignore
78+
if (depcheck.special[special]) {
79+
// @ts-ignore
80+
return depcheck.special[special]
81+
}
82+
83+
// @ts-ignore
84+
if (hoverSpecials[special] && hoverSpecials[special].special) {
85+
// @ts-ignore
86+
return hoverSpecials[special].special
87+
}
88+
}
89+
90+
if (Array.isArray(special)) {
91+
const name = special[0] || ''
92+
const config = special[1] || {}
93+
94+
// @ts-ignore
95+
if (hoverSpecials[name] && hoverSpecials[name].configure) {
96+
// @ts-ignore
97+
return hoverSpecials[name].configure(config)
98+
}
99+
}
100+
101+
throw new Error(`Undefined special ${special}`)
102+
}),
103+
)
104+
}
105+
106+
module.exports = {
107+
resolveConfig,
108+
resolveDetectors,
109+
resolveParsers,
110+
resolveSpecials,
111+
}

src/api/depcheck/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
depcheck: require('./depcheck'),
3+
specials: require('./specials'),
4+
}

src/api/depcheck/specials/babel.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const path = require('path')
2+
3+
/**
4+
*
5+
* @param {string | unknown[]} preset
6+
* @return {string}
7+
*/
8+
const babelDependencyToName = preset => {
9+
if (typeof preset === 'string') return preset
10+
if (Array.isArray(preset) && typeof preset[0] === 'string') return preset[0]
11+
return ''
12+
}
13+
14+
/**
15+
* Configures a special babel parser. Enables detection of babel plugins and presets when they are defined in js files
16+
* (e.g. babel.config.js)
17+
*
18+
* @param {Object} options
19+
* @param {string} options.config
20+
* @param {string} options.env
21+
* @return {function(string, *, string): string[]}
22+
*/
23+
const configure = options => {
24+
const getBabelConfig = require(options.config)
25+
const babelConfig = getBabelConfig({
26+
env: (/** @type {string[]} */ envs) => envs.includes(options.env),
27+
})
28+
29+
/**
30+
* @param {string} filePath
31+
* @param {*} _
32+
* @param {string} dir
33+
* @return {string[]}
34+
*/
35+
const detectBabelPreset = (filePath, _, dir) => {
36+
if (filePath !== path.join(dir, options.config)) {
37+
return []
38+
}
39+
40+
const presets = babelConfig.presets || []
41+
return presets
42+
.map(babelDependencyToName)
43+
.filter((/** @type {string} */ name) => !!name)
44+
}
45+
46+
/**
47+
* @param {string} filePath
48+
* @param {*} _
49+
* @param {string} dir
50+
* @return {string[]}
51+
*/
52+
const detectBabelPlugin = (filePath, _, dir) => {
53+
if (filePath !== path.join(dir, options.config)) {
54+
return []
55+
}
56+
57+
const plugins = babelConfig.plugins || []
58+
return plugins
59+
.map(babelDependencyToName)
60+
.filter((/** @type {string} */ name) => !!name)
61+
}
62+
63+
return (filePath, _, dir) => [
64+
...detectBabelPreset(filePath, _, dir),
65+
...detectBabelPlugin(filePath, _, dir),
66+
]
67+
}
68+
69+
module.exports = {
70+
configure,
71+
}

src/api/depcheck/specials/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
hoverBabel: require('./babel'),
3+
}

0 commit comments

Comments
 (0)