@namchee/dependent
Advanced tools
Comparing version 0.7.1 to 0.7.2
@@ -0,3 +1,11 @@ | ||
import { jest } from '@jest/globals'; | ||
import { cli } from './../src/cli'; | ||
jest.useFakeTimers(); | ||
afterEach(() => { | ||
jest.clearAllTimers(); | ||
}); | ||
describe('CLI test', () => { | ||
@@ -10,16 +18,2 @@ it('should be able to accomodate simple usage', () => { | ||
it('should be able to parse module option', () => { | ||
const args = cli.parseSync('express --module'); | ||
expect(args.package).toBe('express'); | ||
expect(args.module).toBe(true); | ||
}); | ||
it('should be able to parse script option', () => { | ||
const args = cli.parseSync('express --script'); | ||
expect(args.package).toBe('express'); | ||
expect(args.script).toBe(true); | ||
}); | ||
it('should be able to parse file pattern option', () => { | ||
@@ -55,3 +49,3 @@ const args = cli.parseSync('express src/**/*.js'); | ||
it('should be able to accomodate complex usage', () => { | ||
const args = cli.parseSync('express src/**/*.js bin/**/*.js --module'); | ||
const args = cli.parseSync('express src/**/*.js bin/**/*.js'); | ||
@@ -61,4 +55,3 @@ expect(args.package).toBe('express'); | ||
expect(args.files).toContain('bin/**/*.js'); | ||
expect(args.module).toBe(true); | ||
}); | ||
}); |
@@ -0,3 +1,11 @@ | ||
import { jest } from '@jest/globals'; | ||
import { getJSImportLines } from '../../src/parser/js'; | ||
jest.useFakeTimers(); | ||
afterEach(() => { | ||
jest.clearAllTimers(); | ||
}); | ||
describe('ESModule import test', () => { | ||
@@ -103,1 +111,168 @@ it('should be able to parse default imports', () => { | ||
}); | ||
describe('React JSX test', () => { | ||
it('should be able to parse default imports', () => { | ||
const content = `import react from 'react'; | ||
function Home() { | ||
return <h1>Hello World</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse default imports', () => { | ||
const content = `import { useState } from 'react'; | ||
function Home() { | ||
const [ping, setPing] = useState(0); | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse aliased imports', () => { | ||
const content = `import { useState as a } from 'react'; | ||
function Home() { | ||
const [ping, setPing] = a(0); | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse namespace imports', () => { | ||
const content = `import * as React from 'react'; | ||
function Home() { | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse unnamed imports', () => { | ||
const content = `import 'react'; | ||
function Home() { | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse dynamic imports', () => { | ||
const content = `const a = import('react'); | ||
function Home() { | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse imports nested in code', () => { | ||
const content = `import * as React from 'react'; | ||
function Home() { | ||
const isTest = () => { | ||
if (isDevelopment) { | ||
const a = require('b'); | ||
} | ||
} | ||
return <h1>Hello world</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getJSImportLines(content, 'b'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(6); | ||
}); | ||
it('should be able to distinguish false alarms', () => { | ||
const content = `const react = "import * as React from 'react';"; | ||
function Home() { | ||
const isTest = () => { | ||
if (isDevelopment) { | ||
const a = require('b'); | ||
} | ||
} | ||
return <h1>Hello world</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(0); | ||
}); | ||
it('should be able to tolerate CommonJS imports', () => { | ||
const content = `import express from 'express'; | ||
const react = require('react'); | ||
function Home() { | ||
return <h1>Hello world</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(3); | ||
}); | ||
it('should be able to parse class-based components', () => { | ||
const content = `import * as React from 'react'; | ||
export class Welcome extends React.Component { | ||
render() { | ||
return <h1>Hello world</h1>; | ||
} | ||
}`; | ||
const dependants = getJSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to detect nested modules', () => { | ||
const content = `import { defineConfig } from 'windicss/helpers'; | ||
export default defineConfig({});`; | ||
const dependants = getJSImportLines(content, 'windicss'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
}); |
@@ -0,3 +1,11 @@ | ||
import { jest } from '@jest/globals'; | ||
import { getTSImportLines } from '../../src/parser/ts'; | ||
jest.useFakeTimers(); | ||
afterEach(() => { | ||
jest.clearAllTimers(); | ||
}); | ||
describe('TypeScript parser test', () => { | ||
@@ -121,1 +129,187 @@ it('should be able to parse ES modules import', () => { | ||
}); | ||
describe('React TSX test', () => { | ||
it('should be able to parse default imports', () => { | ||
const content = `import React from 'react'; | ||
export type HomeProps = { | ||
foo: string; | ||
bar: 'foo'; | ||
}; | ||
function Home({ foo }: React.PropsWithoutRef<HomeProps>): JSX.Element { | ||
return <h1>{foo}</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse default imports', () => { | ||
const content = `import * as React from 'react'; | ||
import { useState } from 'react'; | ||
export type HomeProps = { | ||
foo: string; | ||
bar: 'foo'; | ||
}; | ||
function Home({ foo }: React.PropsWithoutRef<HomeProps>): JSX.Element { | ||
const [baz, setBaz] = useState(foo); | ||
return <h1>{foo}</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(2); | ||
expect(dependants[0]).toBe(1); | ||
expect(dependants[1]).toBe(2); | ||
}); | ||
it('should be able to parse aliased imports', () => { | ||
const content = `import * as React from 'react'; | ||
import { useState as a } from 'react'; | ||
export type HomeProps = { | ||
foo: string; | ||
bar: 'foo'; | ||
}; | ||
function Home({ foo }: React.PropsWithoutRef<HomeProps>): JSX.Element { | ||
const [baz, setBaz] = a(foo); | ||
return <h1>{foo}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(2); | ||
expect(dependants[0]).toBe(1); | ||
expect(dependants[1]).toBe(2); | ||
}); | ||
it('should be able to parse namespace imports', () => { | ||
const content = `import * as React from 'react'; | ||
function Home(): JSX.Element { | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse unnamed imports', () => { | ||
const content = `import 'react'; | ||
function Home(): JSX.Element { | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse dynamic imports', () => { | ||
const content = `const a = import('react'); | ||
function Home(): JSX.Element { | ||
return <h1>{ping}</h1>; | ||
} | ||
export default Home;` | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to parse imports nested in code', () => { | ||
const content = `import * as React from 'react'; | ||
function Home(): JSX.Element { | ||
const isTest = () => { | ||
if (isDevelopment) { | ||
const a = require('b'); | ||
} | ||
} | ||
return <h1>Hello world</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getTSImportLines(content, 'b'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(6); | ||
}); | ||
it('should be able to distinguish false alarms', () => { | ||
const content = `const react = "import * as React from 'react';"; | ||
function Home(): JSX.Element { | ||
const isTest = () => { | ||
if (isDevelopment) { | ||
const a = require('b'); | ||
} | ||
} | ||
return <h1>Hello world</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(0); | ||
}); | ||
it('should be able to tolerate CommonJS imports', () => { | ||
const content = `import express from 'express'; | ||
const react = require('react'); | ||
function Home(): JSX.Element { | ||
return <h1>Hello world</h1>; | ||
} | ||
export default Home;`; | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(3); | ||
}); | ||
it('should be able to parse class-based components', () => { | ||
const content = `import * as React from 'react'; | ||
export class Welcome extends React.Component { | ||
render() { | ||
return <h1>Hello world</h1>; | ||
} | ||
}`; | ||
const dependants = getTSImportLines(content, 'react'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
it('should be able to detect nested modules', () => { | ||
const content = `import { defineConfig } from 'windicss/helpers'; | ||
export default defineConfig({});`; | ||
const dependants = getTSImportLines(content, 'windicss'); | ||
expect(dependants.length).toBe(1); | ||
expect(dependants[0]).toBe(1); | ||
}); | ||
}); |
@@ -0,4 +1,12 @@ | ||
import { jest } from '@jest/globals'; | ||
import { getDependantFiles } from '../src/import'; | ||
import { ProjectFile } from './../src/types'; | ||
jest.useFakeTimers(); | ||
afterEach(() => { | ||
jest.clearAllTimers(); | ||
}); | ||
describe('Parser tolerance test', () => { | ||
@@ -5,0 +13,0 @@ it('should throw an error when silent is false', () => { |
#!/usr/bin/env node | ||
import e from"ora";import t from"chalk";import n from"yargs";import{hideBin as r}from"yargs/helpers";import{resolve as s,basename as i}from"path";import{existsSync as o,readFileSync as a}from"fs";import{spawn as c}from"child_process";import l from"glob";import{parse as p,Parser as d}from"acorn";import{simple as u,base as g}from"acorn-walk";import h from"typescript";import m from"acorn-jsx";function f(e,t,n,r){return new(n||(n=Promise))((function(s,i){function o(e){try{c(r.next(e))}catch(e){i(e)}}function a(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?s(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,a)}c((r=r.apply(e,t||[])).next())}))}const y=n(r(process.argv)).scriptName("dependent").command("$0 <package> [files...]","Analyze package usage in your project directory.").usage("Usage: $0 <package> [files...]").positional("package",{alias:"p",type:"string",description:"Package name to be analyzed."}).positional("files",{alias:"f",type:"string",description:"Files to be analyzed in glob pattern relative to the current project directory.",default:["!(node_modules|__tests__|test)/**/*!(.spec|test).js","!(node_modules|__tests__|test)/**/*!(.spec|test).mjs","!(node_modules|__tests__|test)/**/*!(.spec|test).ts","!(node_modules|__tests__|test)/**/*!(.spec|test).jsx","!(node_modules|__tests__|test)/**/*!(.spec|test).tsx","*!(.spec|test).js","*!(.spec|test).mjs","*!(.spec|test).ts","*!(.spec|test).jsx","*!(.spec|test).tsx"]}).options({silent:{alias:"s",describe:"Skip all unreadable and unparseable files instead of throwing errors",type:"boolean",default:!1,demandOption:!1},table:{alias:"t",describe:"Print the output in table format",type:"boolean",default:!1,demandOption:!1}});const x=d.extend(m());const S={js:function(e,t){return function(e,t){const n=[];return u(e,{ImportExpression(e){var r;const s=e;"Literal"===s.source.type&&(null===(r=s.source.value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)},ImportDeclaration(e){var r;const s=e;"Literal"===s.source.type&&(null===(r=s.source.value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)},CallExpression(e){var r;const s=e;"Identifier"===s.callee.type&&"require"===s.callee.name&&"Literal"===s.arguments[0].type&&(null===(r=s.arguments[0].value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)}}),n}(p(e,{ecmaVersion:"latest",locations:!0,allowHashBang:!0,sourceType:"module"}),t)},ts:function(e,t){return function(e,t){const n=[],r=s=>{switch(s.kind){case h.SyntaxKind.ImportDeclaration:{const r=s.moduleSpecifier;r.kind===h.SyntaxKind.StringLiteral&&r.getText().slice(1,-1).startsWith(t)&&n.push(e.getLineAndCharacterOfPosition(s.getStart()).line+1);break}case h.SyntaxKind.CallExpression:{const r=s,i=r.expression,o=r.arguments,a=i.kind===h.SyntaxKind.ImportKeyword&&1===o.length&&o[0].kind===h.SyntaxKind.StringLiteral&&o[0].getText().slice(1,-1).startsWith(t),c=i.kind===h.SyntaxKind.Identifier&&"require"===i.getText()&&1===o.length&&o[0].kind===h.SyntaxKind.StringLiteral&&o[0].getText().slice(1,-1).startsWith(t);(a||c)&&n.push(e.getLineAndCharacterOfPosition(s.getStart()).line+1);break}}h.forEachChild(s,r)};return r(e),n}(h.createSourceFile("",e,h.ScriptTarget.Latest,!0),t)},jsx:function(e,t){return function(e,t){const n=[];return u(e,{ImportExpression(e){var r;const s=e;"Literal"===s.source.type&&(null===(r=s.source.value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)},ImportDeclaration(e){var r;const s=e;"Literal"===s.source.type&&(null===(r=s.source.value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)},CallExpression(e){var r;const s=e;"Identifier"===s.callee.type&&"require"===s.callee.name&&"Literal"===s.arguments[0].type&&(null===(r=s.arguments[0].value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)}},Object.assign(Object.assign({},g),{JSXElement:()=>{}})),n}(x.parse(e,{ecmaVersion:"latest",locations:!0,allowHashBang:!0,sourceType:"module"}),t)},tsx:function(e,t){return function(e,t){const n=[],r=s=>{switch(s.kind){case h.SyntaxKind.ImportDeclaration:{const r=s.moduleSpecifier;r.kind===h.SyntaxKind.StringLiteral&&r.getText().slice(1,-1).startsWith(t)&&n.push(e.getLineAndCharacterOfPosition(s.getStart()).line+1);break}case h.SyntaxKind.CallExpression:{const r=s,i=r.expression,o=r.arguments,a=i.kind===h.SyntaxKind.ImportKeyword&&1===o.length&&o[0].kind===h.SyntaxKind.StringLiteral&&o[0].getText().slice(1,-1).startsWith(t),c=i.kind===h.SyntaxKind.Identifier&&"require"===i.getText()&&1===o.length&&o[0].kind===h.SyntaxKind.StringLiteral&&o[0].getText().slice(1,-1).startsWith(t);(a||c)&&n.push(e.getLineAndCharacterOfPosition(s.getStart()).line+1);break}}h.forEachChild(s,r)};return r(e),n}(h.createSourceFile("",e,h.ScriptTarget.Latest,!0,h.ScriptKind.TSX),t)}};function v(e){if(!(e in S))throw new Error(`.${e} files are currently not supported`);return S[e]}function k(e,n,r){console.log("\n"+t.cyanBright(`📦 There are ${e.length} files in this project that depends on '${n}'`)),e.length&&(r?function(e){const t=e.map((e=>({"File name":e.name,"File path":e.path,"Line number":e.lineNumbers.join(", ")})));console.table(t)}(e):function(e){e.forEach((({name:e,path:n,lineNumbers:r})=>{console.log(t.cyan(` └── ${e}:${r.join(", ")} → ${n}`))}))}(e))}f(void 0,void 0,void 0,(function*(){const n=y.parseSync(),r=e().start();try{const e=n.package;r.text=t.greenBright("Scanning project directory...");const p=function(){const e=s(process.cwd(),"package.json");if(!o(e))throw new Error("The current project directory is not a NodeJS-based project");try{const t=JSON.parse(a(e,"utf-8"));return{name:t.name,dependencies:t.dependencies,devDependencies:t.devDependencies,peerDependencies:t.peerDependencies}}catch(e){throw new Error("Invalid package.json schema")}}(),d=n.silent,u=n.table;r.text=t.greenBright("Checking package installation..."),function(e,t){if(!(Object.keys(t.dependencies||{}).includes(e)||Object.keys(t.devDependencies||{}).includes(e)||Object.keys(t.peerDependencies||{}).includes(e)))throw new Error(`Package ${e} is not defined in this project`)}(e,p),yield function(e){return new Promise(((t,n)=>{c(/^win/.test(process.platform)?"npm.cmd":"npm",["ls",e]).stdout.on("data",(r=>{r.includes(e)&&0!==r.lastIndexOf(e)?t():n(new Error(`Package ${e} is not installed in this project`))}))}))}(e),r.text=t.greenBright("Analyzing package dependency...");const g=function(e,t,{silent:n}){const r=[];for(const s of e){let e=s.name.split(".").pop();"mjs"===e&&(e="js");try{const n=v(e)(s.content,t);n.length&&r.push({name:s.name,path:s.path,lineNumbers:n})}catch(e){const t=e;if(n)continue;throw new Error(`Failed to parse ${s.path}: ${t.message}`)}}return r}(function(e,t){const n=l.sync(`{${e.join(",")}}`,{silent:!0}),r=[];for(const e of n)try{const t=i(e),n=a(e,"utf-8");r.push({name:t,path:e,content:n})}catch(n){if(t)continue;throw new Error(`Failed to read ${e}`)}return r}(n.files,d),e,{silent:d});r.succeed(t.greenBright("Analysis completed successfully")),k(g,e,u)}catch(e){const n=e;r.fail(t.redBright(n.message)),console.log(t.cyanBright("Terminating..."))}})); | ||
import e from"ora";import t from"chalk";import n from"yargs";import r,{resolve as o,basename as i}from"path";import{existsSync as s,readFileSync as a}from"fs";import{spawn as c}from"child_process";import l from"glob";import{Parser as p}from"acorn";import{simple as d,base as u}from"acorn-walk";import m from"acorn-jsx";import g from"global-dirs";const f=n(process.argv.slice(2)).scriptName("dependent").command("$0 <package> [files...]","Analyze package usage in your project directory.").usage("Usage: $0 <package> [files...]").positional("package",{alias:"p",type:"string",description:"Package name to be analyzed."}).positional("files",{alias:"f",type:"string",description:"Files to be analyzed in glob pattern relative to the current project directory.",default:["!(node_modules)/**/*.js","!(node_modules)/**/*.mjs","!(node_modules)/**/*.ts","!(node_modules)/**/*.jsx","!(node_modules)/**/*.tsx","*.js","*.mjs","*.ts","*.jsx","*.tsx"]}).options({silent:{alias:"s",describe:"Skip all unreadable and unparseable files instead of throwing errors",type:"boolean",default:!1,demandOption:!1},table:{alias:"t",describe:"Print the output in table format",type:"boolean",default:!1,demandOption:!1}});const h=p.extend(m());function y(e,t){return function(e,t){const n=[];return d(e,{ImportExpression(e){var r;const o=e;"Literal"===o.source.type&&(null===(r=o.source.value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)},ImportDeclaration(e){var r;const o=e;"Literal"===o.source.type&&(null===(r=o.source.value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)},CallExpression(e){var r;const o=e;"Identifier"===o.callee.type&&"require"===o.callee.name&&"Literal"===o.arguments[0].type&&(null===(r=o.arguments[0].value)||void 0===r?void 0:r.toString().startsWith(t))&&n.push(e.loc.start.line)}},Object.assign(Object.assign({},u),{JSXElement:()=>{}})),n}(h.parse(e,{ecmaVersion:"latest",locations:!0,allowHashBang:!0,sourceType:"module"}),t)}let b;try{const e=["typescript","lib","typescript.js"],t=new URL(r.posix.resolve("node_modules",...e),import.meta.url),n=new URL(r.posix.resolve(g.npm.packages,...e),import.meta.url),o=new URL(r.posix.resolve(g.yarn.packages,...e),import.meta.url),i=await Promise.allSettled([import(t.toString()),import(n.toString()),import(o.toString())]);for(const e of i)if("fulfilled"===e.status){b=e.value.default;break}}catch(e){}function j(e,t){if(!b)throw new Error("No typescript parsers available");return function(e,t){const n=[],r=o=>{switch(o.kind){case b.SyntaxKind.ImportDeclaration:{const r=o.moduleSpecifier;10===r.kind&&r.getText().slice(1,-1).startsWith(t)&&n.push(e.getLineAndCharacterOfPosition(o.getStart()).line+1);break}case b.SyntaxKind.CallExpression:{const r=o,i=r.expression,s=r.arguments,a=i.kind===b.SyntaxKind.ImportKeyword&&1===s.length&&s[0].kind===b.SyntaxKind.StringLiteral&&s[0].getText().slice(1,-1).startsWith(t),c=i.kind===b.SyntaxKind.Identifier&&"require"===i.getText()&&1===s.length&&s[0].kind===b.SyntaxKind.StringLiteral&&s[0].getText().slice(1,-1).startsWith(t);(a||c)&&n.push(e.getLineAndCharacterOfPosition(o.getStart()).line+1);break}}b.forEachChild(o,r)};return r(e),n}(b.createSourceFile("",e,b.ScriptTarget.Latest,!0,b.ScriptKind.TSX),t)}const k={js:y,ts:j,jsx:y,tsx:j};function x(e){if(!(e in k))throw new Error(`.${e} files are currently not supported`);return k[e]}function w(e,n,r){console.log("\n"+t.cyanBright(`📦 There are ${e.length} files in this project that depends on '${n}'`)),e.length&&(r?function(e){const t=e.map((e=>({"File name":e.name,"File path":e.path,"Line number":e.lineNumbers.join(", ")})));console.table(t)}(e):function(e){e.forEach((({name:e,path:n,lineNumbers:r})=>{console.log(t.cyan(` └── ${e}:${r.join(", ")} → ${n}`))}))}(e))}(async()=>{const n=f.parseSync(),r=e().start();try{const e=n.package;r.text=t.greenBright("Scanning project directory...");const p=function(){const e=o(process.cwd(),"package.json");if(!s(e))throw new Error("The current project directory is not a NodeJS-based project");try{const t=JSON.parse(a(e,"utf-8"));return{name:t.name,dependencies:t.dependencies,devDependencies:t.devDependencies,peerDependencies:t.peerDependencies}}catch(e){throw new Error("Invalid package.json schema")}}(),d=n.silent,u=n.table;r.text=t.greenBright("Checking package installation..."),function(e,t){if(!(Object.keys(t.dependencies||{}).includes(e)||Object.keys(t.devDependencies||{}).includes(e)||Object.keys(t.peerDependencies||{}).includes(e)))throw new Error(`Package ${e} is not defined in this project`)}(e,p),await function(e){return new Promise(((t,n)=>{c(/^win/.test(process.platform)?"npm.cmd":"npm",["ls",e]).stdout.on("data",(r=>{r.includes(e)&&0!==r.lastIndexOf(e)?t():n(new Error(`Package ${e} is not installed in this project`))}))}))}(e);const m=function(e,t){const n=l.sync(`{${e.join(",")}}`,{silent:!0}),r=[];for(const e of n)try{const t=i(e),n=a(e,"utf-8");r.push({name:t,path:e,content:n})}catch(n){if(t)continue;throw new Error(`Failed to read ${e}`)}return r}(n.files,d);r.text=t.greenBright("Analyzing package dependency...");const g=function(e,t,{silent:n}){const r=[];for(const o of e){let e=o.name.split(".").pop();"mjs"===e&&(e="js");try{const n=x(e)(o.content,t);n.length&&r.push({name:o.name,path:o.path,lineNumbers:n})}catch(e){const t=e;if(n)continue;throw new Error(`Failed to parse ${o.path}: ${t.message}`)}}return r}(m,e,{silent:d});r.succeed(t.greenBright("Analysis completed successfully")),w(g,e,u)}catch(e){const n=e;r.fail(t.redBright(n.message)),console.log(t.cyanBright("Terminating..."))}})(); |
@@ -0,1 +1,15 @@ | ||
# v0.7.2 (Sun Aug 29 2021) | ||
#### 🐛 Bug Fix | ||
- fix: Reconfigure jest to be able to execute native ES modules tests [#35](https://github.com/Namchee/dependent/pull/35) ([@Namchee](https://github.com/Namchee)) | ||
- refactor: Migrate typescript parser into external packages [#32](https://github.com/Namchee/dependent/pull/32) ([@Namchee](https://github.com/Namchee)) | ||
- refactor: Unify js extended parsers with base parserr [#30](https://github.com/Namchee/dependent/pull/30) ([@Namchee](https://github.com/Namchee)) | ||
#### Authors: 1 | ||
- Cristopher ([@Namchee](https://github.com/Namchee)) | ||
--- | ||
# v0.7.1 (Sun Aug 08 2021) | ||
@@ -2,0 +16,0 @@ |
@@ -5,8 +5,17 @@ import type { Config } from '@jest/types'; | ||
const config: Config.InitialOptions = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
// https://jestjs.io/docs/ecmascript-modules | ||
transform: {}, | ||
verbose: true, | ||
transform: {}, | ||
// https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/#use-esm-presets; 'manual configuration' didn't work | ||
preset: 'ts-jest/presets/js-with-ts-esm', | ||
extensionsToTreatAsEsm: ['.ts'], | ||
timers: 'fake', | ||
maxWorkers: 2, | ||
globals: { | ||
'ts-jest': { | ||
useESM: true, | ||
}, | ||
}, | ||
}; | ||
export default config; |
{ | ||
"name": "@namchee/dependent", | ||
"version": "0.7.1", | ||
"version": "0.7.2", | ||
"description": "Simple utility CLI tool to analyze which files are using a Node dependency 🚀", | ||
@@ -21,4 +21,4 @@ "repository": "git@github.com:Namchee/dependent.git", | ||
"build:watch": "rollup -c rollup.config.js -w", | ||
"test": "jest", | ||
"test:watch": "jest --watch" | ||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", | ||
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" | ||
}, | ||
@@ -31,4 +31,4 @@ "dependencies": { | ||
"glob": "^7.1.7", | ||
"global-dirs": "^3.0.0", | ||
"ora": "^5.4.1", | ||
"typescript": "^4.3.5", | ||
"yargs": "^17.0.1" | ||
@@ -48,2 +48,3 @@ }, | ||
"auto": "^10.30.0", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^7.29.0", | ||
@@ -54,3 +55,3 @@ "eslint-config-google": "^0.14.0", | ||
"eslint-plugin-jsdoc": "^35.4.0", | ||
"jest": "^27.0.6", | ||
"jest": "^27.1.0", | ||
"prettier": "^2.3.2", | ||
@@ -63,3 +64,4 @@ "rollup": "^2.56.0", | ||
"stylelint-config-standard": "^22.0.0", | ||
"ts-jest": "^27.0.4" | ||
"ts-jest": "^27.0.5", | ||
"typescript": "^4.3.5" | ||
}, | ||
@@ -66,0 +68,0 @@ "engines": { |
@@ -39,2 +39,8 @@ # Dependent | ||
Alternatively, you can execute `dependent` without installing it by using `npx`. Below is the example of executing `dependent` with `npx` | ||
```bash | ||
npx @namchee/dependent foo | ||
``` | ||
> This package can only be executed on NodeJS 12 or later. | ||
@@ -41,0 +47,0 @@ |
@@ -13,5 +13,4 @@ import typescript from 'rollup-plugin-typescript2'; | ||
output: [ | ||
// will drop this later | ||
{ file: 'bin/index.js', format: 'es' }, | ||
], | ||
} |
import yargs from 'yargs'; | ||
import { hideBin } from 'yargs/helpers'; | ||
@@ -7,3 +6,3 @@ /** | ||
*/ | ||
export const cli = yargs(hideBin(process.argv)) | ||
export const cli = yargs(process.argv.slice(2)) | ||
.scriptName('dependent') | ||
@@ -26,12 +25,12 @@ .command( | ||
default: [ | ||
'!(node_modules|__tests__|test)/**/*!(.spec|test).js', | ||
'!(node_modules|__tests__|test)/**/*!(.spec|test).mjs', | ||
'!(node_modules|__tests__|test)/**/*!(.spec|test).ts', | ||
'!(node_modules|__tests__|test)/**/*!(.spec|test).jsx', | ||
'!(node_modules|__tests__|test)/**/*!(.spec|test).tsx', | ||
'*!(.spec|test).js', | ||
'*!(.spec|test).mjs', | ||
'*!(.spec|test).ts', | ||
'*!(.spec|test).jsx', | ||
'*!(.spec|test).tsx', | ||
'!(node_modules)/**/*.js', | ||
'!(node_modules)/**/*.mjs', | ||
'!(node_modules)/**/*.ts', | ||
'!(node_modules)/**/*.jsx', | ||
'!(node_modules)/**/*.tsx', | ||
'*.js', | ||
'*.mjs', | ||
'*.ts', | ||
'*.jsx', | ||
'*.tsx', | ||
], | ||
@@ -38,0 +37,0 @@ }) |
import { getParser } from './parser'; | ||
import { DependantFile, ParserOptions, ProjectFile } from './types'; | ||
import type { DependantFile, ParserOptions, ProjectFile } from './types'; | ||
@@ -11,4 +11,2 @@ /** | ||
* @param {ParserOptions} options Parsing options | ||
* @param {boolean} options.module `true` if all files should | ||
* be parsed as ES modules, `false` otherwise. | ||
* @param {boolean} options.silent `true` if the parser | ||
@@ -34,3 +32,2 @@ * should ignore invalid files, `false` otherwise. | ||
const parse = getParser(ext); | ||
const isDependant = parse(file.content, dependency); | ||
@@ -40,3 +37,3 @@ | ||
dependant.push( | ||
{ name: file.name, path: file.path, lineNumbers: isDependant } | ||
{ name: file.name, path: file.path, lineNumbers: isDependant }, | ||
); | ||
@@ -43,0 +40,0 @@ } |
@@ -15,3 +15,2 @@ #!/usr/bin/env node | ||
const args = cli.parseSync(); | ||
const spinner = ora().start(); | ||
@@ -33,5 +32,6 @@ | ||
const files = getProjectFiles(args.files, silent); | ||
spinner.text = chalk.greenBright('Analyzing package dependency...'); | ||
const files = getProjectFiles(args.files, silent); | ||
const dependant = getDependantFiles( | ||
@@ -38,0 +38,0 @@ files, |
@@ -5,4 +5,2 @@ import { FileParser } from '../types'; | ||
import { getTSImportLines } from './ts'; | ||
import { getJSXImportLines } from './jsx'; | ||
import { getTSXImportLines } from './tsx'; | ||
@@ -13,7 +11,7 @@ /** | ||
*/ | ||
const PARSER_MAP: Record<string, FileParser> = { | ||
const PARSER_FUNCTIONS: Record<string, FileParser> = { | ||
js: getJSImportLines, | ||
ts: getTSImportLines, | ||
jsx: getJSXImportLines, | ||
tsx: getTSXImportLines, | ||
jsx: getJSImportLines, | ||
tsx: getTSImportLines, | ||
}; | ||
@@ -30,7 +28,7 @@ | ||
export function getParser(ext: string): FileParser { | ||
if (!(ext in PARSER_MAP)) { | ||
if (!(ext in PARSER_FUNCTIONS)) { | ||
throw new Error(`.${ext} files are currently not supported`); | ||
} | ||
return PARSER_MAP[ext]; | ||
return PARSER_FUNCTIONS[ext]; | ||
} |
@@ -1,4 +0,6 @@ | ||
import { parse } from 'acorn'; | ||
import { simple } from 'acorn-walk'; | ||
import { Parser } from 'acorn'; | ||
import { simple, base } from 'acorn-walk'; | ||
import jsx from 'acorn-jsx'; | ||
import type { Node } from 'acorn'; | ||
@@ -12,2 +14,4 @@ import type { | ||
const parser = Parser.extend(jsx()); | ||
/** | ||
@@ -62,2 +66,7 @@ * Parse native JavaScript nodes for imports to `dependency` | ||
}, | ||
}, { | ||
...base, | ||
JSXElement: () => { | ||
// empty | ||
}, | ||
}); | ||
@@ -80,3 +89,3 @@ | ||
): number[] { | ||
const node: Node = parse(content, { | ||
const node: Node = parser.parse(content, { | ||
ecmaVersion: 'latest', | ||
@@ -83,0 +92,0 @@ locations: true, |
@@ -1,7 +0,48 @@ | ||
import ts from 'typescript'; | ||
import globalDirectories from 'global-dirs'; | ||
import path from 'path'; | ||
import type { | ||
SourceFile, | ||
Node, | ||
ImportDeclaration, | ||
CallExpression, | ||
} from 'typescript'; | ||
let ts: typeof import('typescript'); | ||
try { | ||
const basePath = ['typescript', 'lib', 'typescript.js']; | ||
const localPath = new URL( | ||
path.posix.resolve('node_modules', ...basePath), | ||
import.meta.url, | ||
); | ||
const npmPath = new URL( | ||
path.posix.resolve(globalDirectories.npm.packages, ...basePath), | ||
import.meta.url, | ||
); | ||
const yarnPath = new URL( | ||
path.posix.resolve(globalDirectories.yarn.packages, ...basePath), | ||
import.meta.url, | ||
); | ||
const imports = await Promise.allSettled([ | ||
import(localPath.toString()), | ||
import(npmPath.toString()), | ||
import(yarnPath.toString()), | ||
]); | ||
for (const impor of imports) { | ||
if (impor.status === 'fulfilled') { | ||
ts = impor.value.default as typeof import('typescript'); | ||
break; | ||
} | ||
} | ||
} catch (err) { | ||
/* ignore for now */ | ||
} | ||
/** | ||
* Parse TypeScript node for imports to `dependency` | ||
* | ||
* @param {ts.SourceFile} sourceNode AST representation of the file | ||
* @param {SourceFile} sourceNode AST representation of the file | ||
* @param {string} dependency Package name | ||
@@ -12,3 +53,3 @@ * @returns {number[]} List of line numbers where `dependency` | ||
function parseNode( | ||
sourceNode: ts.SourceFile, | ||
sourceNode: SourceFile, | ||
dependency: string, | ||
@@ -18,10 +59,10 @@ ): number[] { | ||
const walk = (node: ts.Node) => { | ||
const walk = (node: Node) => { | ||
switch (node.kind) { | ||
case ts.SyntaxKind.ImportDeclaration: { | ||
const specifier = (node as ts.ImportDeclaration) | ||
const specifier = (node as ImportDeclaration) | ||
.moduleSpecifier; | ||
if ( | ||
specifier.kind === ts.SyntaxKind.StringLiteral && | ||
specifier.kind === 10 && | ||
specifier.getText().slice(1, -1).startsWith(dependency) | ||
@@ -38,3 +79,3 @@ ) { | ||
case ts.SyntaxKind.CallExpression: { | ||
const callExpr = node as ts.CallExpression; | ||
const callExpr = node as CallExpression; | ||
@@ -85,2 +126,6 @@ const expression = callExpr.expression; | ||
): number[] { | ||
if (!ts) { | ||
throw new Error('No typescript parsers available'); | ||
} | ||
const node = ts.createSourceFile( | ||
@@ -91,2 +136,3 @@ '', | ||
true, | ||
ts.ScriptKind.TSX, | ||
); | ||
@@ -93,0 +139,0 @@ |
@@ -5,3 +5,4 @@ { | ||
"outDir": "./bin", | ||
"module": "es2020" | ||
"target": "es2017", | ||
"module": "esnext" | ||
}, | ||
@@ -12,4 +13,5 @@ "include": [ | ||
"exclude": [ | ||
"node_modules" | ||
"node_modules", | ||
"**/*.test.ts" | ||
] | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
144
125500
27
37
1134
+ Addedglobal-dirs@^3.0.0
+ Addedglobal-dirs@3.0.1(transitive)
+ Addedini@2.0.0(transitive)
- Removedtypescript@^4.3.5
- Removedtypescript@4.9.5(transitive)