Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
More tests + some hardening
Browse files Browse the repository at this point in the history
  • Loading branch information
voxpelli committed Oct 18, 2021
1 parent 29a90d8 commit 7d86505
Show file tree
Hide file tree
Showing 23 changed files with 830 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/coverage/**/*
/test/*/*
/test/*/**/*
5 changes: 3 additions & 2 deletions lib/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const {
* @property {string} path
* @property {string[]} [entries]
* @property {boolean} [noDefaultEntries]
* @property {import('./extensions').Extensions|string[]} [extensions]
* @property {import('./extensions').ExtensionsInput|string[]} [extensions]
* @property {import('./extensions').Detective|string} [detective]
* @property {boolean} [builtins]
*/
Expand All @@ -23,7 +23,7 @@ const {
* @returns {Promise<import('./parse').ParseResult>}
*/
const check = async function (opts) {
if (!opts) throw new TypeError('Requires an opts argument to be set')
if (!opts) throw new Error('Requires an opts argument to be set')

const {
builtins,
Expand All @@ -35,6 +35,7 @@ const check = async function (opts) {
} = opts

if (!targetPath) throw new Error('Requires a path to be set')
if (typeof targetPath !== 'string') throw new TypeError(`Requires path to be a string, got: ${typeof targetPath}`)

const {
pkgPath,
Expand Down
12 changes: 7 additions & 5 deletions lib/cli-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ args._ = args._.map((string) => {
})

/**
* @param {*} arg
* @returns {import('./extensions').Extensions}
* @param {string|string[]} arg
* @returns {import('./extensions').ExtensionsInput}
*/
const extensions = function (arg) {
if (!arg) return {}

/** @type {import('./extensions').Extensions} */
/** @type {import('./extensions').ExtensionsInput} */
const extensions = {}

if (typeof arg === 'string') {
Expand All @@ -81,7 +81,7 @@ const extensions = function (arg) {
for (const value of arg) {
const parts = value.trim().split(':', 2)

for (const ext of parts[0].split(',')) {
for (const ext of (parts[0] || '').split(',')) {
extensions[ext.charAt(0) === '.' ? ext : '.' + ext] = parts[1]
}
}
Expand Down Expand Up @@ -111,11 +111,13 @@ check({
.then(data => {
const pkg = data.package
const deps = data.used
/** @type {string[]} */
const ignore = [args['i'] || []].flat()
let failed = 0
const options = {
excludeDev: args['dev'] === false,
excludePeer: args['peer'] === false,
ignore: [args['i'] || []].flat()
ignore
}

const runAllTests = !args['extra'] && !args['missing']
Expand Down
6 changes: 6 additions & 0 deletions lib/compare.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const micromatch = require('micromatch')
* @returns {{ allDeps: string[], ignore: string[] }}
*/
const configure = function (pkg, options = {}) {
if (!pkg || typeof pkg !== 'object') throw new TypeError('Expected a pkg object')

const allDeps = [
...Object.keys(pkg.dependencies || {}),
...(options.excludePeer ? [] : Object.keys(pkg.peerDependencies || {})),
Expand All @@ -40,6 +42,8 @@ const missing = function (pkg, deps, options) {
const missing = []
const config = configure(pkg, options)

if (!Array.isArray(deps)) throw new TypeError('Expected a deps array')

for (const used of deps) {
if (!config.allDeps.includes(used) && !micromatch.isMatch(used, config.ignore)) {
missing.push(used)
Expand All @@ -60,6 +64,8 @@ const extra = function (pkg, deps, options) {
const missing = []
const config = configure(pkg, options)

if (!Array.isArray(deps)) throw new TypeError('Expected a deps array')

for (const dep of config.allDeps) {
if (!deps.includes(dep) && !micromatch.isMatch(dep, config.ignore)) {
missing.push(dep)
Expand Down
90 changes: 64 additions & 26 deletions lib/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,35 @@

const { ErrorWithCause } = require('pony-cause')

/** @type {Record<string, string>} */
const extensionMapping = {
'.js': 'precinct/es6',
'.jsx': 'precinct/es6',
'.mjs': 'precinct/es6',
'.cjs': 'precinct/commonjs',
'.ts': 'precinct/ts',
'.tsx': 'precinct/tsx',
}

const noopExtensions = new Set([
'.node',
'.json'
])

/** @typedef {(contents: string) => string[]} Detective */
/** @typedef {{ [extension: string]: Detective | string | undefined }} ExtensionsInput */
/** @typedef {{ [extension: string]: Detective | undefined }} Extensions */

/** @type {Detective} */
const noopDetective = () => []

/**
* @param {string|Detective} [name]
* @returns {Detective}
*/
const getDetective = function (name) {
if (name === '-') return noopDetective

/** @type {string|undefined} */
let precinctType

Expand All @@ -19,32 +40,42 @@ const getDetective = function (name) {
name = undefined
}

try {
if (name) {
if (name) {
try {
// eslint-disable-next-line security/detect-non-literal-require
return typeof name === 'string' ? require(name) : name
} catch (err) {
throw new ErrorWithCause(`Failed to load detective '${name}'`, { cause: err })
}
}

/** @type {(contents: string, options: { type?: string, es6?: { mixedImports: boolean }|undefined}) => string[]} */
// @ts-ignore There is no declaration for the precinct module
const precinct = require('precinct')
/** @type {(contents: string, options: { type?: string, es6?: { mixedImports: boolean }|undefined}) => string[]} */
// @ts-ignore There is no declaration for the precinct module
const precinct = require('precinct')

if (!precinctType) throw new Error('Expected a precinctType, but got none')
if (!precinctType) throw new Error('Expected a "precinct/something", but got "precinct/"')

return (contents) => (precinctType && precinct(contents, {
type: precinctType,
es6: precinctType && ['es6', 'commonjs'].includes(precinctType) ? { mixedImports: true } : undefined
})) || []
} catch (err) {
throw new ErrorWithCause(`Failed to load detective '${name}'`, { cause: err })
}
return (contents) => (precinctType && precinct(contents, {
type: precinctType,
es6: precinctType && ['es6', 'commonjs'].includes(precinctType) ? { mixedImports: true } : undefined
})) || []
}

/** @type {Detective} */
const noopDetective = () => []
/**
* @param {string} extension
* @param {Detective | string | undefined} detective
* @returns {Detective}
*/
const getDetectiveForExtension = function (extension, detective) {
if (noopExtensions.has(extension)) {
return getDetective('-')
}

return getDetective(detective || extensionMapping[extension])
}

/**
* @param {string[]|Extensions|undefined} extensions
* @param {string[]|ExtensionsInput|undefined} extensions
* @param {string|Detective|undefined} detective
* @returns {Extensions}
*/
Expand All @@ -54,22 +85,29 @@ const getExtensions = function (extensions, detective) {

if (Array.isArray(extensions)) {
for (const extension of extensions) {
result[extension] = getDetective(detective)
result[extension] = getDetectiveForExtension(extension, detective)
}
} else if (typeof extensions === 'object') {
for (const extension in extensions) {
result[extension] = getDetective(extensions[extension] || detective)
if (extensions[extension]) {
result[extension] = getDetective(extensions[extension])
} else {
result[extension] = getDetectiveForExtension(extension, detective)
}
}
} else if (extensions) {
throw new TypeError('Requires extensions argument to be an array or object')
}

if (detective && typeof detective !== 'function' && typeof detective !== 'string') {
throw new TypeError('Requires detective to be a string or a function')
}

result['.js'] = result['.js'] || getDetective(detective || 'precinct/es6')
result['.jsx'] = result['.jsx'] || getDetective(detective || 'precinct/es6')
result['.mjs'] = result['.mjs'] || getDetective(detective || 'precinct/es6')
result['.cjs'] = result['.cjs'] || getDetective(detective || 'precinct/commonjs')
result['.ts'] = result['.ts'] || getDetective(detective || 'precinct/ts')
result['.tsx'] = result['.tsx'] || getDetective(detective || 'precinct/tsx')
result['.node'] = result['.node'] || noopDetective
result['.json'] = result['.json'] || noopDetective
if (Object.keys(result).length === 0) {
for (const extension of [...Object.keys(extensionMapping), ...noopExtensions]) {
result[extension] = getDetectiveForExtension(extension, detective)
}
}

return result
}
Expand Down
4 changes: 3 additions & 1 deletion lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ const parse = async function (opts) {

debug('entry paths', paths)

if (paths.length === 0) return Promise.reject(new Error('No entry paths found'))
if (paths.length === 0) {
throw new Error('No entry paths found')
}

/** @type {Array<Promise<void>>} */
const lookups = []
Expand Down
2 changes: 1 addition & 1 deletion lib/resolve-target.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const resolveEntryTarget = async function (targetPath) {
const targetEntries = await resolveGlobbedPath(targetPath, process.cwd())

if (!targetEntries[0]) {
throw new Error('Failed to find package.json, no file to resolve it from')
throw new Error(`Failed to find package.json, path "${targetPath}" does not resolve to any file for "${process.cwd()}"`)
}

const pkgPath = await pkgUp({ cwd: path.dirname(targetEntries[0]) })
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
"clean": "run-p clean:*",
"prepublishOnly": "run-s build",
"sync-gh-actions": "ghat",
"test-ci": "run-s test-cli",
"test:cli:negative:ts": "node cli.js test/mock-negative/abc.ts 2>&1 >/dev/null | grep \"code: @scope/test1\\|\\.json: example, example2\" | wc -l | grep '2' > /dev/null",
"test:cli:negative:simple": "node cli.js test/mock-negative/ 2>&1 >/dev/null | grep \"code: @scope/test1\\|\\.json: example, example2\" | wc -l | grep '2' > /dev/null",
"test-ci": "run-s test:*",
"test:cli:positive:custom-detective": "node cli.js test/mock-positive/ -e js:detective-cjs",
"test:cli:positive:glob": "node cli.js 'test/mock-positive/**/*.js' --no-default-entries",
"test:cli:positive:multi-glob": "node cli.js test/mock-positive/foo.js 'test/mock-positive/*.js' \"test/mock-positive/donkey/*.js\" --no-default-entries",
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ running eg. `dependency-check package.json tests.js --no-default-entries` won't

### --extensions, -e

running `dependency-check ./package.json -e js,jsx:precinct` will resolve require paths to `.js` and `.jsx` paths, and parse using [`precinct`](https://www.npmjs.com/package/precinct).
running `dependency-check ./package.json -e js,cjs:detective` will resolve require paths to `.js` and `.cjs` paths, and parse using [`detective`](https://www.npmjs.com/package/detective). Specifying any extension will disable the default detectives. Specify just `-e js,cjs` to use the standard detective, `-e foo::` to use ignore the extension, `-e js:precinct/es6` to specify a specific `precinct` setting

### --detective

running `dependency-check ./package.json --detective precinct` will `require()` the local `precinct` as the default parser. This can be set per-extension using using `-e`. Defaults to parsing with [`detective`](https://www.npmjs.com/package/detective).
running `dependency-check ./package.json --detective detective` will `require()` the local `detective` as the default parser. This can be set per-extension using using `-e`. Defaults to parsing with [`precinct`](https://www.npmjs.com/package/precinct).

### --json, -j

Expand Down
8 changes: 8 additions & 0 deletions test/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"env": {
"mocha": true
},
"rules": {
"no-unused-expressions": 0
}
}
Loading

0 comments on commit 7d86505

Please sign in to comment.